@mailwoman/corpus 8.6.0 → 9.0.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 +1 -1
- package/out/src/adapter.d.ts +41 -0
- package/out/src/adapter.d.ts.map +1 -1
- package/out/src/adapter.js +46 -1
- package/out/src/adapter.js.map +1 -1
- package/out/src/adapters/ban/adapter.d.ts +4 -3
- package/out/src/adapters/ban/adapter.d.ts.map +1 -1
- package/out/src/adapters/ban/adapter.js +60 -67
- package/out/src/adapters/ban/adapter.js.map +1 -1
- package/out/src/adapters/ban/street-decompose.d.ts.map +1 -1
- package/out/src/adapters/ban/street-decompose.js +18 -25
- package/out/src/adapters/ban/street-decompose.js.map +1 -1
- package/out/src/adapters/fcc-bdc/adapter.d.ts +0 -6
- package/out/src/adapters/fcc-bdc/adapter.d.ts.map +1 -1
- package/out/src/adapters/fcc-bdc/adapter.js +2 -24
- package/out/src/adapters/fcc-bdc/adapter.js.map +1 -1
- package/out/src/adapters/geonames/adapter.d.ts.map +1 -1
- package/out/src/adapters/geonames/adapter.js +70 -76
- package/out/src/adapters/geonames/adapter.js.map +1 -1
- package/out/src/adapters/geonames-postal/adapter.d.ts.map +1 -1
- package/out/src/adapters/geonames-postal/adapter.js +44 -49
- package/out/src/adapters/geonames-postal/adapter.js.map +1 -1
- package/out/src/adapters/gnaf/adapter.d.ts.map +1 -1
- package/out/src/adapters/gnaf/adapter.js +4 -7
- package/out/src/adapters/gnaf/adapter.js.map +1 -1
- package/out/src/adapters/gnaf/assemble.d.ts.map +1 -1
- package/out/src/adapters/gnaf/assemble.js +6 -11
- package/out/src/adapters/gnaf/assemble.js.map +1 -1
- package/out/src/adapters/openaddresses/adapter.d.ts.map +1 -1
- package/out/src/adapters/openaddresses/adapter.js +2 -7
- package/out/src/adapters/openaddresses/adapter.js.map +1 -1
- package/out/src/adapters/overture/adapter.d.ts.map +1 -1
- package/out/src/adapters/overture/adapter.js +3 -7
- package/out/src/adapters/overture/adapter.js.map +1 -1
- package/out/src/adapters/state-hi-schools/adapter.d.ts.map +1 -1
- package/out/src/adapters/state-hi-schools/adapter.js +55 -73
- package/out/src/adapters/state-hi-schools/adapter.js.map +1 -1
- package/out/src/adapters/state-ia-contractors/adapter.d.ts.map +1 -1
- package/out/src/adapters/state-ia-contractors/adapter.js +58 -76
- package/out/src/adapters/state-ia-contractors/adapter.js.map +1 -1
- package/out/src/adapters/state-ny-notaries/adapter.d.ts.map +1 -1
- package/out/src/adapters/state-ny-notaries/adapter.js +67 -85
- package/out/src/adapters/state-ny-notaries/adapter.js.map +1 -1
- package/out/src/adapters/state-tx-notaries/adapter.d.ts.map +1 -1
- package/out/src/adapters/state-tx-notaries/adapter.js +69 -87
- package/out/src/adapters/state-tx-notaries/adapter.js.map +1 -1
- package/out/src/adapters/synth-po-box/adapter.d.ts.map +1 -1
- package/out/src/adapters/synth-po-box/adapter.js +7 -15
- package/out/src/adapters/synth-po-box/adapter.js.map +1 -1
- package/out/src/adapters/tiger/street-decompose.d.ts.map +1 -1
- package/out/src/adapters/tiger/street-decompose.js +18 -27
- package/out/src/adapters/tiger/street-decompose.js.map +1 -1
- package/out/src/adapters/usgov-hrsa-fqhc/adapter.d.ts +3 -3
- package/out/src/adapters/usgov-hrsa-fqhc/adapter.d.ts.map +1 -1
- package/out/src/adapters/usgov-hrsa-fqhc/adapter.js +62 -80
- package/out/src/adapters/usgov-hrsa-fqhc/adapter.js.map +1 -1
- package/out/src/adapters/usgov-imls-pls/adapter.d.ts.map +1 -1
- package/out/src/adapters/usgov-imls-pls/adapter.js +60 -82
- package/out/src/adapters/usgov-imls-pls/adapter.js.map +1 -1
- package/out/src/adapters/usgov-irs-bmf/adapter.d.ts.map +1 -1
- package/out/src/adapters/usgov-irs-bmf/adapter.js +58 -65
- package/out/src/adapters/usgov-irs-bmf/adapter.js.map +1 -1
- package/out/src/adapters/usgov-nad/adapter.d.ts.map +1 -1
- package/out/src/adapters/usgov-nad/adapter.js +5 -8
- package/out/src/adapters/usgov-nad/adapter.js.map +1 -1
- package/out/src/adapters/usgov-nppes/adapter.d.ts.map +1 -1
- package/out/src/adapters/usgov-nppes/adapter.js +58 -76
- package/out/src/adapters/usgov-nppes/adapter.js.map +1 -1
- package/out/src/adapters/usgov-samhsa-treatment-locator/adapter.d.ts.map +1 -1
- package/out/src/adapters/usgov-samhsa-treatment-locator/adapter.js +51 -71
- package/out/src/adapters/usgov-samhsa-treatment-locator/adapter.js.map +1 -1
- package/out/src/adapters/wof-admin-jp/adapter.d.ts.map +1 -1
- package/out/src/adapters/wof-admin-jp/adapter.js +24 -6
- package/out/src/adapters/wof-admin-jp/adapter.js.map +1 -1
- package/out/src/build.d.ts.map +1 -1
- package/out/src/build.js +2 -1
- package/out/src/build.js.map +1 -1
- package/out/src/golden.d.ts +5 -0
- package/out/src/golden.d.ts.map +1 -1
- package/out/src/golden.js +23 -9
- package/out/src/golden.js.map +1 -1
- package/out/src/parquet-wrapper/reader.d.ts +8 -0
- package/out/src/parquet-wrapper/reader.d.ts.map +1 -1
- package/out/src/parquet-wrapper/reader.js +16 -0
- package/out/src/parquet-wrapper/reader.js.map +1 -1
- package/out/src/parquet-wrapper/schema.d.ts +4 -0
- package/out/src/parquet-wrapper/schema.d.ts.map +1 -1
- package/out/src/parquet-wrapper/schema.js.map +1 -1
- package/out/src/parquet.d.ts.map +1 -1
- package/out/src/parquet.js +2 -13
- package/out/src/parquet.js.map +1 -1
- package/out/src/shard-recipes/anchor-absorption.d.ts.map +1 -1
- package/out/src/shard-recipes/anchor-absorption.js +2 -1
- package/out/src/shard-recipes/anchor-absorption.js.map +1 -1
- package/out/src/shard-recipes/country-balanced.d.ts.map +1 -1
- package/out/src/shard-recipes/country-balanced.js +68 -60
- package/out/src/shard-recipes/country-balanced.js.map +1 -1
- package/out/src/shard-recipes/fr-fragment.d.ts.map +1 -1
- package/out/src/shard-recipes/fr-fragment.js +8 -5
- package/out/src/shard-recipes/fr-fragment.js.map +1 -1
- package/out/src/shard-recipes/fr-order.d.ts.map +1 -1
- package/out/src/shard-recipes/fr-order.js +14 -58
- package/out/src/shard-recipes/fr-order.js.map +1 -1
- package/out/src/shard-recipes/german.d.ts.map +1 -1
- package/out/src/shard-recipes/german.js +11 -58
- package/out/src/shard-recipes/german.js.map +1 -1
- package/out/src/shard-recipes/index.d.ts.map +1 -1
- package/out/src/shard-recipes/index.js +2 -0
- package/out/src/shard-recipes/index.js.map +1 -1
- package/out/src/shard-recipes/intersection.d.ts +2 -2
- package/out/src/shard-recipes/intersection.d.ts.map +1 -1
- package/out/src/shard-recipes/intersection.js +24 -62
- package/out/src/shard-recipes/intersection.js.map +1 -1
- package/out/src/shard-recipes/locale.d.ts.map +1 -1
- package/out/src/shard-recipes/locale.js +14 -16
- package/out/src/shard-recipes/locale.js.map +1 -1
- package/out/src/shard-recipes/no-fragment.d.ts.map +1 -1
- package/out/src/shard-recipes/no-fragment.js +8 -5
- package/out/src/shard-recipes/no-fragment.js.map +1 -1
- package/out/src/shard-recipes/no-street-led.d.ts.map +1 -1
- package/out/src/shard-recipes/no-street-led.js +8 -5
- package/out/src/shard-recipes/no-street-led.js.map +1 -1
- package/out/src/shard-recipes/po-box-cedex.d.ts +3 -3
- package/out/src/shard-recipes/po-box-cedex.d.ts.map +1 -1
- package/out/src/shard-recipes/po-box-cedex.js +51 -94
- package/out/src/shard-recipes/po-box-cedex.js.map +1 -1
- package/out/src/shard-recipes/scaffold.d.ts +59 -9
- package/out/src/shard-recipes/scaffold.d.ts.map +1 -1
- package/out/src/shard-recipes/scaffold.js +51 -30
- package/out/src/shard-recipes/scaffold.js.map +1 -1
- package/out/src/shard-recipes/street-affix.d.ts.map +1 -1
- package/out/src/shard-recipes/street-affix.js +56 -82
- package/out/src/shard-recipes/street-affix.js.map +1 -1
- package/out/src/shard-recipes/sub-venue-sources.d.ts +231 -0
- package/out/src/shard-recipes/sub-venue-sources.d.ts.map +1 -0
- package/out/src/shard-recipes/sub-venue-sources.js +669 -0
- package/out/src/shard-recipes/sub-venue-sources.js.map +1 -0
- package/out/src/shard-recipes/sub-venue.d.ts +269 -0
- package/out/src/shard-recipes/sub-venue.d.ts.map +1 -0
- package/out/src/shard-recipes/sub-venue.js +709 -0
- package/out/src/shard-recipes/sub-venue.js.map +1 -0
- package/out/src/shard-recipes/unit.d.ts +1 -1
- package/out/src/shard-recipes/unit.d.ts.map +1 -1
- package/out/src/shard-recipes/unit.js +22 -64
- package/out/src/shard-recipes/unit.js.map +1 -1
- package/out/src/split.d.ts +7 -0
- package/out/src/split.d.ts.map +1 -1
- package/out/src/split.js +7 -0
- package/out/src/split.js.map +1 -1
- package/out/src/synthesize.d.ts.map +1 -1
- package/out/src/synthesize.js +1 -12
- package/out/src/synthesize.js.map +1 -1
- package/out/src/tools/align-shard.d.ts.map +1 -1
- package/out/src/tools/align-shard.js +3 -7
- package/out/src/tools/align-shard.js.map +1 -1
- package/out/src/tools/audit.d.ts.map +1 -1
- package/out/src/tools/audit.js +4 -4
- package/out/src/tools/audit.js.map +1 -1
- package/out/src/tools/corpus-stats.d.ts +2 -2
- package/out/src/tools/corpus-stats.d.ts.map +1 -1
- package/out/src/tools/corpus-stats.js +18 -31
- package/out/src/tools/corpus-stats.js.map +1 -1
- package/out/src/tools/fetch/download.d.ts.map +1 -1
- package/out/src/tools/fetch/download.js +5 -6
- package/out/src/tools/fetch/download.js.map +1 -1
- package/out/src/tools/fetch/imls-pls.d.ts.map +1 -1
- package/out/src/tools/fetch/imls-pls.js +2 -2
- package/out/src/tools/fetch/imls-pls.js.map +1 -1
- package/out/src/tools/fetch/index.d.ts +14 -1
- package/out/src/tools/fetch/index.d.ts.map +1 -1
- package/out/src/tools/fetch/index.js +14 -1
- package/out/src/tools/fetch/index.js.map +1 -1
- package/out/src/tools/fetch/nad.d.ts +1 -1
- package/out/src/tools/fetch/nad.js +1 -1
- package/out/src/tools/fetch/nppes.d.ts.map +1 -1
- package/out/src/tools/fetch/nppes.js +2 -1
- package/out/src/tools/fetch/nppes.js.map +1 -1
- package/out/src/tools/fetch/openaddresses.d.ts +1 -1
- package/out/src/tools/fetch/openaddresses.js +1 -1
- package/out/src/tools/fetch/ourairports.d.ts +45 -0
- package/out/src/tools/fetch/ourairports.d.ts.map +1 -0
- package/out/src/tools/fetch/ourairports.js +123 -0
- package/out/src/tools/fetch/ourairports.js.map +1 -0
- package/out/src/tools/fetch/wikidata-subvenue.d.ts +171 -0
- package/out/src/tools/fetch/wikidata-subvenue.d.ts.map +1 -0
- package/out/src/tools/fetch/wikidata-subvenue.js +275 -0
- package/out/src/tools/fetch/wikidata-subvenue.js.map +1 -0
- package/out/src/tools/golden-expand.d.ts.map +1 -1
- package/out/src/tools/golden-expand.js +18 -16
- package/out/src/tools/golden-expand.js.map +1 -1
- package/out/src/tools/golden-promote.d.ts.map +1 -1
- package/out/src/tools/golden-promote.js +12 -6
- package/out/src/tools/golden-promote.js.map +1 -1
- package/out/src/tools/golden-relabel-street.d.ts +196 -0
- package/out/src/tools/golden-relabel-street.d.ts.map +1 -0
- package/out/src/tools/golden-relabel-street.js +513 -0
- package/out/src/tools/golden-relabel-street.js.map +1 -0
- package/out/src/tools/index.d.ts +4 -0
- package/out/src/tools/index.d.ts.map +1 -1
- package/out/src/tools/index.js +4 -0
- package/out/src/tools/index.js.map +1 -1
- package/out/src/tools/jsonl-to-parquet.d.ts.map +1 -1
- package/out/src/tools/jsonl-to-parquet.js +3 -2
- package/out/src/tools/jsonl-to-parquet.js.map +1 -1
- package/out/src/tools/lint-shard-vocab.d.ts.map +1 -1
- package/out/src/tools/lint-shard-vocab.js +3 -2
- package/out/src/tools/lint-shard-vocab.js.map +1 -1
- package/out/src/tools/lint-shard.d.ts +3 -3
- package/out/src/tools/lint-shard.d.ts.map +1 -1
- package/out/src/tools/lint-shard.js +23 -32
- package/out/src/tools/lint-shard.js.map +1 -1
- package/out/src/tools/overlay-manifest.d.ts.map +1 -1
- package/out/src/tools/overlay-manifest.js +2 -1
- package/out/src/tools/overlay-manifest.js.map +1 -1
- package/out/src/tools/overture-subvenue.d.ts +112 -0
- package/out/src/tools/overture-subvenue.d.ts.map +1 -0
- package/out/src/tools/overture-subvenue.js +144 -0
- package/out/src/tools/overture-subvenue.js.map +1 -0
- package/out/src/tools/shard-kryptonite.d.ts +1 -1
- package/out/src/tools/shard-kryptonite.d.ts.map +1 -1
- package/out/src/tools/shard-kryptonite.js +5 -4
- package/out/src/tools/shard-kryptonite.js.map +1 -1
- package/out/src/tools/shard-translit.d.ts +6 -3
- package/out/src/tools/shard-translit.d.ts.map +1 -1
- package/out/src/tools/shard-translit.js +8 -6
- package/out/src/tools/shard-translit.js.map +1 -1
- package/out/src/tools/sub-venue-lexicon.d.ts +507 -0
- package/out/src/tools/sub-venue-lexicon.d.ts.map +1 -0
- package/out/src/tools/sub-venue-lexicon.js +817 -0
- package/out/src/tools/sub-venue-lexicon.js.map +1 -0
- package/out/src/tools/sub-venue-promotions.d.ts +94 -0
- package/out/src/tools/sub-venue-promotions.d.ts.map +1 -0
- package/out/src/tools/sub-venue-promotions.js +266 -0
- package/out/src/tools/sub-venue-promotions.js.map +1 -0
- package/out/src/wof-json.d.ts +2 -2
- package/out/src/wof-json.d.ts.map +1 -1
- package/out/src/wof-json.js +5 -8
- package/out/src/wof-json.js.map +1 -1
- package/package.json +8 -8
- package/src/adapter.ts +58 -1
- package/src/adapters/ban/adapter.ts +55 -65
- package/src/adapters/ban/street-decompose.ts +17 -25
- package/src/adapters/fcc-bdc/adapter.ts +2 -33
- package/src/adapters/geonames/adapter.ts +64 -72
- package/src/adapters/geonames-postal/adapter.ts +42 -50
- package/src/adapters/gnaf/adapter.ts +4 -7
- package/src/adapters/gnaf/assemble.ts +6 -10
- package/src/adapters/openaddresses/adapter.ts +2 -7
- package/src/adapters/overture/adapter.ts +3 -6
- package/src/adapters/state-hi-schools/adapter.ts +50 -73
- package/src/adapters/state-ia-contractors/adapter.ts +52 -76
- package/src/adapters/state-ny-notaries/adapter.ts +61 -85
- package/src/adapters/state-tx-notaries/adapter.ts +60 -84
- package/src/adapters/synth-po-box/adapter.ts +7 -17
- package/src/adapters/tiger/street-decompose.ts +21 -31
- package/src/adapters/usgov-hrsa-fqhc/adapter.ts +61 -84
- package/src/adapters/usgov-imls-pls/adapter.ts +54 -82
- package/src/adapters/usgov-irs-bmf/adapter.ts +53 -63
- package/src/adapters/usgov-nad/adapter.ts +5 -8
- package/src/adapters/usgov-nppes/adapter.ts +51 -75
- package/src/adapters/usgov-samhsa-treatment-locator/adapter.ts +45 -71
- package/src/adapters/wof-admin-jp/adapter.ts +34 -8
- package/src/build.ts +2 -1
- package/src/golden.ts +24 -9
- package/src/parquet-wrapper/reader.ts +19 -0
- package/src/parquet-wrapper/schema.ts +4 -0
- package/src/parquet.ts +3 -16
- package/src/shard-recipes/anchor-absorption.ts +2 -1
- package/src/shard-recipes/country-balanced.ts +69 -70
- package/src/shard-recipes/fr-fragment.ts +10 -7
- package/src/shard-recipes/fr-order.ts +13 -63
- package/src/shard-recipes/german.ts +12 -65
- package/src/shard-recipes/index.ts +2 -0
- package/src/shard-recipes/intersection.ts +32 -60
- package/src/shard-recipes/locale.ts +14 -16
- package/src/shard-recipes/no-fragment.ts +10 -7
- package/src/shard-recipes/no-street-led.ts +10 -7
- package/src/shard-recipes/po-box-cedex.ts +68 -119
- package/src/shard-recipes/scaffold.ts +82 -28
- package/src/shard-recipes/street-affix.ts +58 -95
- package/src/shard-recipes/sub-venue-sources.ts +895 -0
- package/src/shard-recipes/sub-venue.ts +982 -0
- package/src/shard-recipes/unit.ts +22 -70
- package/src/split.ts +7 -0
- package/src/synthesize.ts +1 -15
- package/src/tools/align-shard.ts +3 -6
- package/src/tools/audit.ts +6 -5
- package/src/tools/corpus-stats.ts +25 -33
- package/src/tools/fetch/download.ts +7 -5
- package/src/tools/fetch/imls-pls.ts +2 -2
- package/src/tools/fetch/index.ts +14 -1
- package/src/tools/fetch/nad.ts +1 -1
- package/src/tools/fetch/nppes.ts +2 -1
- package/src/tools/fetch/openaddresses.ts +1 -1
- package/src/tools/fetch/ourairports.ts +166 -0
- package/src/tools/fetch/wikidata-subvenue.ts +386 -0
- package/src/tools/golden-expand.ts +18 -13
- package/src/tools/golden-promote.ts +15 -6
- package/src/tools/golden-relabel-street.ts +748 -0
- package/src/tools/index.ts +4 -0
- package/src/tools/jsonl-to-parquet.ts +3 -2
- package/src/tools/lint-shard-vocab.ts +3 -2
- package/src/tools/lint-shard.ts +33 -36
- package/src/tools/overlay-manifest.ts +2 -1
- package/src/tools/overture-subvenue.ts +215 -0
- package/src/tools/shard-kryptonite.ts +5 -4
- package/src/tools/shard-translit.ts +12 -7
- package/src/tools/sub-venue-lexicon.ts +1250 -0
- package/src/tools/sub-venue-promotions.ts +330 -0
- package/src/wof-json.ts +5 -8
package/src/tools/index.ts
CHANGED
|
@@ -17,8 +17,12 @@ export * from "./fetch/download.ts"
|
|
|
17
17
|
export * from "./fetch/index.ts"
|
|
18
18
|
export * from "./golden-expand.ts"
|
|
19
19
|
export * from "./golden-promote.ts"
|
|
20
|
+
export * from "./golden-relabel-street.ts"
|
|
20
21
|
export * from "./jsonl-to-parquet.ts"
|
|
21
22
|
export * from "./lint-shard.ts"
|
|
22
23
|
export * from "./lint-shard-vocab.ts"
|
|
23
24
|
export * from "./shard-kryptonite.ts"
|
|
25
|
+
export * from "./overture-subvenue.ts"
|
|
26
|
+
export * from "./sub-venue-lexicon.ts"
|
|
27
|
+
export * from "./sub-venue-promotions.ts"
|
|
24
28
|
export * from "./shard-translit.ts"
|
|
@@ -36,6 +36,7 @@ import { unlink } from "node:fs/promises"
|
|
|
36
36
|
import { tmpdir } from "node:os"
|
|
37
37
|
import { join } from "node:path"
|
|
38
38
|
|
|
39
|
+
import { parseJSONStrict } from "@mailwoman/core/objects"
|
|
39
40
|
import { TextSpliterator } from "spliterator"
|
|
40
41
|
|
|
41
42
|
const REQUIRED_COLUMNS = [
|
|
@@ -168,7 +169,7 @@ export async function jsonlToParquet(
|
|
|
168
169
|
let lineNo = 0
|
|
169
170
|
|
|
170
171
|
// TextSpliterator, not JSONSpliterator: the staging write below streams the RAW line bytes to
|
|
171
|
-
// DuckDB verbatim (
|
|
172
|
+
// DuckDB verbatim (the parse here only validates), so a re-serialized JSONSpliterator row would
|
|
172
173
|
// defeat the point. CRLF is handled by the existing `rawLine.trim()` (strips a trailing \r),
|
|
173
174
|
// same as readline's crlfDelay:Infinity did.
|
|
174
175
|
for await (const rawLine of TextSpliterator.fromAsync(options.input)) {
|
|
@@ -176,7 +177,7 @@ export async function jsonlToParquet(
|
|
|
176
177
|
const line = rawLine.trim()
|
|
177
178
|
|
|
178
179
|
if (!line) continue
|
|
179
|
-
const row =
|
|
180
|
+
const row = parseJSONStrict<Record<string, unknown>>(line)
|
|
180
181
|
assertSpanTriple(row, lineNo)
|
|
181
182
|
// Write the validated line verbatim; DuckDB's `read_json` projects to the explicit `columns`
|
|
182
183
|
// map below (extra keys dropped, absent keys → NULL — matching the Python `row.get(c)`).
|
|
@@ -37,6 +37,7 @@
|
|
|
37
37
|
import { readdirSync } from "node:fs"
|
|
38
38
|
import { join } from "node:path"
|
|
39
39
|
|
|
40
|
+
import { tryParsingJSON } from "@mailwoman/core/objects"
|
|
40
41
|
import { dataRootPath } from "@mailwoman/core/utils"
|
|
41
42
|
|
|
42
43
|
/**
|
|
@@ -162,8 +163,8 @@ async function readRows(con: DuckDBConnection, path: string): Promise<CorpusRow[
|
|
|
162
163
|
const rows: CorpusRow[] = []
|
|
163
164
|
|
|
164
165
|
for (const r of raw) {
|
|
165
|
-
const tokens =
|
|
166
|
-
const labels =
|
|
166
|
+
const tokens = tryParsingJSON(String(r.tokens))
|
|
167
|
+
const labels = tryParsingJSON(String(r.labels))
|
|
167
168
|
|
|
168
169
|
if (!Array.isArray(tokens) || !Array.isArray(labels)) continue
|
|
169
170
|
|
package/src/tools/lint-shard.ts
CHANGED
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
* mappings — flagging matches.
|
|
24
24
|
* 5. **Basic sanity.** Truncated rows (tokens.length !== labels.length), all-O rows >90% of shard.
|
|
25
25
|
*
|
|
26
|
-
* Output: markdown report on stdout, optional JSON sidecar via `
|
|
26
|
+
* Output: markdown report on stdout, optional JSON sidecar via `outJSON`. The command exits 0 if
|
|
27
27
|
* no errors, 1 if any errors (warnings don't gate). Per the design, the MANIFEST entry for a
|
|
28
28
|
* flagged shard should require `lint_acknowledged: true` before training consumes it.
|
|
29
29
|
*
|
|
@@ -35,10 +35,13 @@
|
|
|
35
35
|
* [--out-json /tmp/lint-report.json]
|
|
36
36
|
*/
|
|
37
37
|
|
|
38
|
-
import { execSync } from "node:child_process"
|
|
39
38
|
import { existsSync, readFileSync, writeFileSync } from "node:fs"
|
|
40
39
|
import { fileURLToPath } from "node:url"
|
|
41
40
|
|
|
41
|
+
import { parseJSONStrict } from "@mailwoman/core/objects"
|
|
42
|
+
|
|
43
|
+
import { ParquetReader } from "../parquet-wrapper/index.ts"
|
|
44
|
+
|
|
42
45
|
/**
|
|
43
46
|
* Occurrences of a forbidden label before it is reported — one or two are noise, five is a pattern.
|
|
44
47
|
*/
|
|
@@ -100,7 +103,7 @@ export interface LintCorpusShardOptions {
|
|
|
100
103
|
/**
|
|
101
104
|
* Write a JSON sidecar of the flags + summary here.
|
|
102
105
|
*/
|
|
103
|
-
|
|
106
|
+
outJSON?: string
|
|
104
107
|
}
|
|
105
108
|
|
|
106
109
|
interface CorpusStats {
|
|
@@ -123,31 +126,24 @@ interface LintRulesFile {
|
|
|
123
126
|
rules: LintRule[]
|
|
124
127
|
}
|
|
125
128
|
|
|
126
|
-
|
|
129
|
+
// The reader's `ParquetRecordLike` constraint is `Record<string, unknown>`, and TypeScript gives a type
|
|
130
|
+
// alias an implicit index signature where an interface has none.
|
|
131
|
+
// oxlint-disable-next-line typescript/consistent-type-definitions -- see above
|
|
132
|
+
type ShardRow = {
|
|
127
133
|
tokens: string[]
|
|
128
134
|
labels: string[]
|
|
129
135
|
}
|
|
130
136
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
sys.stdout.write(json.dumps({"tokens": tokens_col[i], "labels": labels_col[i]}) + "\\n")
|
|
140
|
-
`
|
|
141
|
-
|
|
142
|
-
const buf = execSync(`python3`, { input: py, maxBuffer: 1024 * 1024 * 1024 })
|
|
143
|
-
const rows: ShardRow[] = []
|
|
144
|
-
|
|
145
|
-
for (const line of buf.toString("utf8").split("\n")) {
|
|
146
|
-
if (!line) continue
|
|
147
|
-
rows.push(JSON.parse(line))
|
|
148
|
-
}
|
|
137
|
+
/**
|
|
138
|
+
* Stream a shard's `tokens`/`labels` columns.
|
|
139
|
+
*
|
|
140
|
+
* Projected rather than read whole: parquet is columnar, so the twelve unused columns are never touched. The rows feed
|
|
141
|
+
* straight into {@link statsFromShard} and are never all resident.
|
|
142
|
+
*/
|
|
143
|
+
async function* streamShard(shardPath: string): AsyncIterable<ShardRow> {
|
|
144
|
+
await using reader = await ParquetReader.openFile<ShardRow>(shardPath)
|
|
149
145
|
|
|
150
|
-
|
|
146
|
+
yield* reader.project("tokens", "labels")
|
|
151
147
|
}
|
|
152
148
|
|
|
153
149
|
interface ShardStats {
|
|
@@ -158,16 +154,18 @@ interface ShardStats {
|
|
|
158
154
|
allORows: number
|
|
159
155
|
}
|
|
160
156
|
|
|
161
|
-
function statsFromShard(rows: ShardRow
|
|
157
|
+
async function statsFromShard(rows: AsyncIterable<ShardRow>): Promise<ShardStats> {
|
|
162
158
|
const out: ShardStats = {
|
|
163
|
-
rowCount:
|
|
159
|
+
rowCount: 0,
|
|
164
160
|
tokens: new Map(),
|
|
165
161
|
bigrams: new Map(),
|
|
166
162
|
truncatedRows: 0,
|
|
167
163
|
allORows: 0,
|
|
168
164
|
}
|
|
169
165
|
|
|
170
|
-
for (const row of rows) {
|
|
166
|
+
for await (const row of rows) {
|
|
167
|
+
out.rowCount++
|
|
168
|
+
|
|
171
169
|
if (row.tokens.length !== row.labels.length) {
|
|
172
170
|
out.truncatedRows++
|
|
173
171
|
|
|
@@ -474,27 +472,26 @@ function renderReport(
|
|
|
474
472
|
/**
|
|
475
473
|
* Lint a shard against corpus stats + the anti-pattern rules; print the markdown report to stdout.
|
|
476
474
|
*/
|
|
477
|
-
export function lintCorpusShard(
|
|
475
|
+
export async function lintCorpusShard(
|
|
478
476
|
options: LintCorpusShardOptions,
|
|
479
477
|
report?: (line: string) => void
|
|
480
|
-
): LintCorpusShardSummary {
|
|
478
|
+
): Promise<LintCorpusShardSummary> {
|
|
481
479
|
const rulesPath = options.rulesPath ?? defaultRulesPath()
|
|
482
480
|
report?.(`Reading corpus stats from ${options.statsPath}...`)
|
|
483
|
-
const corpus
|
|
481
|
+
const corpus = parseJSONStrict<CorpusStats>(readFileSync(options.statsPath, "utf8"))
|
|
484
482
|
|
|
485
483
|
report?.(
|
|
486
484
|
` ${corpus.row_count} rows from ${corpus.shard_paths.length} shard(s); ${Object.keys(corpus.tokens).length} tokens, ${Object.keys(corpus.bigrams).length} bigrams`
|
|
487
485
|
)
|
|
488
486
|
|
|
489
487
|
report?.(`Reading shard from ${options.shardPath}...`)
|
|
490
|
-
const rows = readShard(options.shardPath)
|
|
491
|
-
report?.(` ${rows.length} rows`)
|
|
492
488
|
|
|
493
|
-
|
|
494
|
-
|
|
489
|
+
const shard = await statsFromShard(streamShard(options.shardPath))
|
|
490
|
+
|
|
491
|
+
report?.(` ${shard.rowCount} rows`)
|
|
495
492
|
|
|
496
493
|
report?.(`Loading rules from ${rulesPath}...`)
|
|
497
|
-
const rulesFile
|
|
494
|
+
const rulesFile = parseJSONStrict<LintRulesFile>(readFileSync(rulesPath, "utf8"))
|
|
498
495
|
|
|
499
496
|
report?.(`Running checks...`)
|
|
500
497
|
|
|
@@ -514,9 +511,9 @@ export function lintCorpusShard(
|
|
|
514
511
|
writeFileSync(options.outMd, rendered)
|
|
515
512
|
}
|
|
516
513
|
|
|
517
|
-
if (options.
|
|
514
|
+
if (options.outJSON) {
|
|
518
515
|
writeFileSync(
|
|
519
|
-
options.
|
|
516
|
+
options.outJSON,
|
|
520
517
|
JSON.stringify(
|
|
521
518
|
{
|
|
522
519
|
shard: options.shardPath,
|
|
@@ -26,6 +26,7 @@
|
|
|
26
26
|
import { readFileSync, statSync, writeFileSync } from "node:fs"
|
|
27
27
|
import { join } from "node:path"
|
|
28
28
|
|
|
29
|
+
import { parseJSONStrict } from "@mailwoman/core/objects"
|
|
29
30
|
import { sha256Hex } from "@mailwoman/core/utils"
|
|
30
31
|
|
|
31
32
|
interface ShardDescriptor {
|
|
@@ -96,7 +97,7 @@ export interface OverlayManifestOptions {
|
|
|
96
97
|
}
|
|
97
98
|
|
|
98
99
|
export async function assembleOverlayManifest(args: OverlayManifestOptions): Promise<void> {
|
|
99
|
-
const base =
|
|
100
|
+
const base = parseJSONStrict<BaseManifest>(readFileSync(args.base, "utf8"))
|
|
100
101
|
|
|
101
102
|
if (base.shards.some((s) => s.source === args.source)) {
|
|
102
103
|
console.log(`WARN: base already contains source '${args.source}' — is this the right base?`)
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @copyright Sister Software
|
|
3
|
+
* @license AGPL-3.0
|
|
4
|
+
* @author Teffen Ellis, et al.
|
|
5
|
+
*
|
|
6
|
+
* Overture Places source for the sub-venue lexicon (#35 wave 2) — read the sub-venue-bearing
|
|
7
|
+
* category slices of `poi.db` (spatial layer #1, `$MAILWOMAN_DATA_ROOT/poi/poi.db`) and yield
|
|
8
|
+
* {@link SubVenueHarvestRow}s the lexicon builder consumes exactly like an OSM extract.
|
|
9
|
+
*
|
|
10
|
+
* ── WHY OVERTURE AT ALL, when the OSM extractor already exists ────────────────────────────────────
|
|
11
|
+
* Wave 1 measured `concourse` at 21 real Overture rows against 4 in the whole Great Britain OSM
|
|
12
|
+
* extract, 3 of which are a street called CONCOURSE WAY. Overture's `airport_terminal` category is
|
|
13
|
+
* curated venue-interior naming; OSM's `aeroway=terminal` is a building footprint that usually
|
|
14
|
+
* carries the AIRPORT's name. The two sources fail differently, so both are read.
|
|
15
|
+
*
|
|
16
|
+
* ── THE CATEGORY SET IS MEASURED, NOT GUESSED ────────────────────────────────────────────────────
|
|
17
|
+
* A full scan of all 13,681,698 rows (2026-08-05, poi.db vintage 2026-05-20.0) counted, per
|
|
18
|
+
* category, how many named rows carry a designator token. The ranking is NOT what a category name
|
|
19
|
+
* predicts — `gas_station` leads the whole table with 12,996 hits, every one of them the token
|
|
20
|
+
* `station` inside "Holiday Station" / "Chevron Station Seward", and `shoe_store` contributes 708
|
|
21
|
+
* hits of `wing` because Red Wing sells boots. {@link OVERTURE_SUBVENUE_CATEGORIES} is the four
|
|
22
|
+
* categories whose hits survived reading the distribution; the rejects are listed below it so nobody
|
|
23
|
+
* re-proposes them.
|
|
24
|
+
*
|
|
25
|
+
* ── poi.db IS FOUR COUNTRIES ─────────────────────────────────────────────────────────────────────
|
|
26
|
+
* Measured the same day: US 11,521,612 / CA 794,418 / FR 721,352 / MX 644,316, and nothing else. The
|
|
27
|
+
* shipped layer is not a world gazetteer, so Overture can attest en-US, en-CA, fr-FR and es-MX
|
|
28
|
+
* surfaces and NOTHING ELSE. Every non-Latin designator the corpus task asks for — `ターミナル`,
|
|
29
|
+
* `Halle`, `Flügel` — has to come from the OSM leg. Do not read a zero count here as evidence of
|
|
30
|
+
* absence in the world; it is evidence of absence in four countries.
|
|
31
|
+
*
|
|
32
|
+
* ── The row shape fits; the PROVENANCE STAMP did not ─────────────────────────────────────────────
|
|
33
|
+
* Wave 1's `OSMSubVenueRow` was written to accept a non-OSM row, and it does: an Overture row is
|
|
34
|
+
* `{ designatorID, name }` with no `ref` and no `localizedNames`. What did NOT fit is
|
|
35
|
+
* `extractAttestedPhrases`, which hardcoded `osm:name` as the surface's `source`. Feeding Overture
|
|
36
|
+
* rows through it unchanged would have labelled every Overture surface as OSM-attested — a
|
|
37
|
+
* provenance lie, and under ODbL a consequential one. Hence the `source`/`region` options on that
|
|
38
|
+
* function and the source-neutral {@link SubVenueHarvestRow} name.
|
|
39
|
+
*/
|
|
40
|
+
|
|
41
|
+
import { DatabaseSync } from "node:sqlite"
|
|
42
|
+
|
|
43
|
+
import { DatabaseClient } from "@mailwoman/core/kysley/client"
|
|
44
|
+
|
|
45
|
+
import type { SubVenueHarvestRow } from "./sub-venue-lexicon.ts"
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Overture Places category → the designator its rows attest.
|
|
49
|
+
*
|
|
50
|
+
* Each entry carries the 2026-08-05 full-scan measurement in a comment: `rows` is the category's total, `hits` is how
|
|
51
|
+
* many of its named rows contain any designator token, and the token list is the top of that distribution.
|
|
52
|
+
*
|
|
53
|
+
* REJECTED, with the number that rejected them — do not re-add without a fresh census:
|
|
54
|
+
*
|
|
55
|
+
* - `gas_station` (12,996 hits) — all `station` inside a brand name (Holiday Station, Chevron Station Seward).
|
|
56
|
+
* - `fire_station` (10,377) — same, plus 330 `hall` from fire halls, which are venues rather than sub-venues.
|
|
57
|
+
* - `town_hall` (5,528) — `City Hall` is a whole building, the `venue` tier, not interior structure.
|
|
58
|
+
* - `building_supply_store` (4,030) — `building` inside "Allied Building Products".
|
|
59
|
+
* - `jehovahs_witness_place_of_worship` (2,334) — every hit is "Kingdom Hall of Jehovah's Witnesses".
|
|
60
|
+
* - `shoe_store` (909, of which 708 `wing`) — Red Wing. This one is a CONFOUND BOARD entry, not a source.
|
|
61
|
+
* - `college_university` (3,697) — 2,082 `campus`, but the row names the whole institution; `campus_building` is the
|
|
62
|
+
* interior slice and is kept instead.
|
|
63
|
+
* - `airport` (6,000 rows, 4,302 hits) — 4,071 of them are the token `airport` in the aerodrome's own name. Venue tier,
|
|
64
|
+
* already covered by OurAirports, and it drowns the interior signal.
|
|
65
|
+
* - `transport_interchange` (1 row), `rail_facility_or_service` (81 rows, 2 hits) — too small to matter.
|
|
66
|
+
* - `public_transit_facility_or_service` (2,274 rows, 185 hits) — 161 are `station` naming the station itself.
|
|
67
|
+
*/
|
|
68
|
+
export const OVERTURE_SUBVENUE_CATEGORIES: Readonly<Record<string, string>> = {
|
|
69
|
+
// 1,022 rows / 669 hits — terminal 266, concourse 19, gate 13, hall 4. The densest interior naming
|
|
70
|
+
// in the layer and the reason this reader exists.
|
|
71
|
+
airport_terminal: "terminal",
|
|
72
|
+
// 7,366 rows / 3,382 hits — hall 2,134, building 1,019, campus 209. US academic halls and numbered
|
|
73
|
+
// campus buildings: "UAA Cuddy Hall", "UAA Science Building". The row is a building ON a campus, so
|
|
74
|
+
// `campus` is its CONTEXT designator; the phrase found inside the name decides the record it
|
|
75
|
+
// attests, which for these rows is mostly `hall`.
|
|
76
|
+
campus_building: "campus",
|
|
77
|
+
// 443 rows / 301 hits — pier 282, terminal 17.
|
|
78
|
+
pier: "pier",
|
|
79
|
+
// 388 rows / 66 hits — airport 36, terminal 20, concourse 5, gate 3. Small, but the hits are almost
|
|
80
|
+
// all genuine ("Delta Sky Club Concourse B").
|
|
81
|
+
airport_lounge: "terminal",
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* The `poi` and `poi_category_codes` columns this reader touches, declared LOCALLY.
|
|
86
|
+
*
|
|
87
|
+
* `@mailwoman/resolver-wof-sqlite` owns the full `POIDatabase` interface, and `@mailwoman/corpus` does not depend on it
|
|
88
|
+
* — the same dependency-direction call `sub-venue-lexicon.ts` makes for `@mailwoman/osm`'s row type. This is a
|
|
89
|
+
* read-only projection of four columns; `overture-subvenue.test.ts` builds a fixture with exactly this DDL, so a column
|
|
90
|
+
* rename upstream fails a test here rather than throwing at runtime against a 3.9 GB database nobody has in CI.
|
|
91
|
+
*/
|
|
92
|
+
interface POIReadDatabase {
|
|
93
|
+
poi: {
|
|
94
|
+
category_id: number
|
|
95
|
+
name: string | null
|
|
96
|
+
country: string
|
|
97
|
+
}
|
|
98
|
+
poi_category_codes: {
|
|
99
|
+
id: number
|
|
100
|
+
category: string
|
|
101
|
+
}
|
|
102
|
+
layer_manifest: {
|
|
103
|
+
name: string
|
|
104
|
+
source_vintage: string
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* The layer's `source_vintage` — the Overture release the rows came from (`2026-05-20.0`), for the lexicon's
|
|
110
|
+
* `sources[]`.
|
|
111
|
+
*
|
|
112
|
+
* Read off the database rather than passed in, because a vintage a caller types is a vintage that goes stale silently.
|
|
113
|
+
* The layer-contract tables are part of every layer database by construction — see `docs/engineering/reference/
|
|
114
|
+
* layer-contract.mdx` — so there is no version of poi.db where this is absent.
|
|
115
|
+
*/
|
|
116
|
+
export async function readOvertureLayerVintage(databasePath: string): Promise<string> {
|
|
117
|
+
using kdb = new DatabaseClient<POIReadDatabase>({
|
|
118
|
+
database: new DatabaseSync(databasePath, { readOnly: true }),
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
const row = await kdb
|
|
122
|
+
.selectFrom("layer_manifest")
|
|
123
|
+
.select("source_vintage")
|
|
124
|
+
.where("name", "=", "poi")
|
|
125
|
+
.executeTakeFirst()
|
|
126
|
+
|
|
127
|
+
return row?.source_vintage ?? ""
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export interface ReadOvertureSubVenuesOptions {
|
|
131
|
+
/**
|
|
132
|
+
* Path to `poi.db`. Typically `dataRootPath("poi", "poi.db")`.
|
|
133
|
+
*/
|
|
134
|
+
databasePath: string
|
|
135
|
+
/**
|
|
136
|
+
* Category → designator map. Defaults to {@link OVERTURE_SUBVENUE_CATEGORIES}.
|
|
137
|
+
*/
|
|
138
|
+
categories?: Readonly<Record<string, string>>
|
|
139
|
+
/**
|
|
140
|
+
* Restrict to these ISO 3166-1 alpha-2 countries. Omit for all four the layer carries.
|
|
141
|
+
*/
|
|
142
|
+
countries?: readonly string[]
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* One Overture row plus the country it came from — {@link SubVenueHarvestRow} widened by the field the layer has and an
|
|
147
|
+
* OSM extract does not (a Geofabrik extract's country is a property of the invocation, so OSM rows carry `country:
|
|
148
|
+
* ""`).
|
|
149
|
+
*/
|
|
150
|
+
export interface OvertureSubVenueRow extends SubVenueHarvestRow {
|
|
151
|
+
/**
|
|
152
|
+
* ISO 3166-1 alpha-2, straight off the Overture partition.
|
|
153
|
+
*/
|
|
154
|
+
country: string
|
|
155
|
+
/**
|
|
156
|
+
* The Overture category the row was read from, so a surface's provenance survives past the designator.
|
|
157
|
+
*/
|
|
158
|
+
category: string
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Read the sub-venue-bearing category slices of `poi.db`.
|
|
163
|
+
*
|
|
164
|
+
* Cold path, already async, no interface constraint — so Kysely, per the repo's inline-SQL rule.
|
|
165
|
+
*
|
|
166
|
+
* `category_id` is the SECOND component of poi.db's clustered `(h3_cell, category_id, …)` primary key, which reads like
|
|
167
|
+
* a full 13.7M-row scan and is not one: SQLite skip-scans the leading `h3_cell` and the whole four-category read
|
|
168
|
+
* returned 9,219 rows in **1.4 s** on the shipped layer (2026-08-05). A JS-side scan of every row to survey the same
|
|
169
|
+
* question took 52 s, which is the number to remember if you are tempted to filter in JS instead.
|
|
170
|
+
*/
|
|
171
|
+
export async function readOvertureSubVenues(options: ReadOvertureSubVenuesOptions): Promise<OvertureSubVenueRow[]> {
|
|
172
|
+
const categories = options.categories ?? OVERTURE_SUBVENUE_CATEGORIES
|
|
173
|
+
|
|
174
|
+
using kdb = new DatabaseClient<POIReadDatabase>({
|
|
175
|
+
database: new DatabaseSync(options.databasePath, { readOnly: true }),
|
|
176
|
+
})
|
|
177
|
+
|
|
178
|
+
const codes = await kdb
|
|
179
|
+
.selectFrom("poi_category_codes")
|
|
180
|
+
.select(["id", "category"])
|
|
181
|
+
.where("category", "in", Object.keys(categories))
|
|
182
|
+
.execute()
|
|
183
|
+
|
|
184
|
+
if (!codes.length) return []
|
|
185
|
+
|
|
186
|
+
const designatorByID = new Map<number, string>()
|
|
187
|
+
const categoryByID = new Map<number, string>()
|
|
188
|
+
|
|
189
|
+
for (const code of codes) {
|
|
190
|
+
const designator = categories[code.category]
|
|
191
|
+
|
|
192
|
+
if (!designator) continue
|
|
193
|
+
designatorByID.set(code.id, designator)
|
|
194
|
+
categoryByID.set(code.id, code.category)
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
let query = kdb
|
|
198
|
+
.selectFrom("poi")
|
|
199
|
+
.select(["category_id", "name", "country"])
|
|
200
|
+
.where("category_id", "in", [...designatorByID.keys()])
|
|
201
|
+
.where("name", "is not", null)
|
|
202
|
+
|
|
203
|
+
if (options.countries?.length) {
|
|
204
|
+
query = query.where("country", "in", [...options.countries])
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
const rows = await query.execute()
|
|
208
|
+
|
|
209
|
+
return rows.map((row) => ({
|
|
210
|
+
designatorID: designatorByID.get(row.category_id)!,
|
|
211
|
+
name: row.name,
|
|
212
|
+
country: row.country,
|
|
213
|
+
category: categoryByID.get(row.category_id)!,
|
|
214
|
+
}))
|
|
215
|
+
}
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* MANIFEST. corpus-v0.4.0 is a pure adapter-addition revision: it points at every shard from
|
|
8
8
|
* v0.3.0 plus the new kryptonite shard(s). No v0.3.0 bytes are touched or re-shuffled.
|
|
9
9
|
*
|
|
10
|
-
* See docs/
|
|
10
|
+
* See docs/engineering/reference/CORPUS_V0_4_0_GENERATION.md for the why; that doc also pins the
|
|
11
11
|
* DeepSeek model version + prompt versions used to produce the JSONL.
|
|
12
12
|
*
|
|
13
13
|
* Invoke via `mailwoman corpus shard kryptonite \
|
|
@@ -20,7 +20,8 @@ import { existsSync, readFileSync, writeFileSync } from "node:fs"
|
|
|
20
20
|
import { mkdir } from "node:fs/promises"
|
|
21
21
|
import { join } from "node:path"
|
|
22
22
|
|
|
23
|
-
import {
|
|
23
|
+
import { parseJSONStrict } from "@mailwoman/core/objects"
|
|
24
|
+
import { JSONSpliterator } from "spliterator"
|
|
24
25
|
|
|
25
26
|
import { alignRow } from "../align.ts"
|
|
26
27
|
import type { ShardManifest } from "../parquet.ts"
|
|
@@ -42,7 +43,7 @@ export interface ShardKryptoniteOptions {
|
|
|
42
43
|
}
|
|
43
44
|
|
|
44
45
|
async function* canonicalRows(jsonl: string, corpusVersion: string): AsyncIterable<CanonicalRow> {
|
|
45
|
-
for await (const raw of
|
|
46
|
+
for await (const raw of JSONSpliterator.fromAsync<Record<string, unknown>>(jsonl)) {
|
|
46
47
|
// Strip sidecar underscore-prefixed fields the generator left behind for debugging.
|
|
47
48
|
const components = raw["components"] as Record<string, string>
|
|
48
49
|
|
|
@@ -111,7 +112,7 @@ export async function buildKryptoniteShard(
|
|
|
111
112
|
}
|
|
112
113
|
|
|
113
114
|
// Compose the final corpus-v0.4.0 manifest: every shard from base + the new shard(s).
|
|
114
|
-
const base =
|
|
115
|
+
const base = parseJSONStrict<ShardManifest>(readFileSync(options.baseManifest, "utf8"))
|
|
115
116
|
|
|
116
117
|
const combined: ShardManifest = {
|
|
117
118
|
corpus_version: corpusVersion,
|
|
@@ -16,10 +16,10 @@
|
|
|
16
16
|
* - Each shard is written to `train/part-translit-<slug>.parquet` (distinct from kryptonite's
|
|
17
17
|
* `part-0000.parquet`, which v0.4.0's first builder already produced).
|
|
18
18
|
* - Inherits the path-canonicalization fix flagged in Thread B's postmortem: v0.3.0 shard paths are
|
|
19
|
-
* rewritten from
|
|
19
|
+
* rewritten from `$MAILWOMAN_DATA_ROOT/...` to `/data/...` in the combined MANIFEST so
|
|
20
20
|
* all paths share one container-friendly form.
|
|
21
21
|
*
|
|
22
|
-
* See docs/
|
|
22
|
+
* See docs/engineering/reference/CORPUS_V0_4_0_GENERATION.md for prompts, model, and the
|
|
23
23
|
* reproducibility contract.
|
|
24
24
|
*
|
|
25
25
|
* Invoke via `mailwoman corpus shard translit \
|
|
@@ -32,7 +32,9 @@ import { existsSync, readFileSync, writeFileSync } from "node:fs"
|
|
|
32
32
|
import { mkdir, stat } from "node:fs/promises"
|
|
33
33
|
import { join } from "node:path"
|
|
34
34
|
|
|
35
|
-
import {
|
|
35
|
+
import { parseJSONStrict } from "@mailwoman/core/objects"
|
|
36
|
+
import { mailwomanDataRoot, sha256File } from "@mailwoman/core/utils"
|
|
37
|
+
import { JSONSpliterator } from "spliterator"
|
|
36
38
|
|
|
37
39
|
import { alignRow } from "../align.ts"
|
|
38
40
|
import { ParquetWriter } from "../parquet-wrapper/index.ts"
|
|
@@ -53,7 +55,10 @@ export interface ShardTranslitOptions {
|
|
|
53
55
|
*/
|
|
54
56
|
canonicalPathPrefix?: string
|
|
55
57
|
/**
|
|
56
|
-
*
|
|
58
|
+
* Prefix the base manifest's shard paths currently carry, to be rewritten to
|
|
59
|
+
* {@link ShardTranslitOptions.canonicalPathPrefix}. Defaults to `mailwomanDataRoot()` with a trailing slash — the
|
|
60
|
+
* root that WROTE those paths. Pass it explicitly when translating a manifest generated under a different
|
|
61
|
+
* `$MAILWOMAN_DATA_ROOT` than the one you are running with.
|
|
57
62
|
*/
|
|
58
63
|
legacyPathPrefix?: string
|
|
59
64
|
}
|
|
@@ -164,7 +169,7 @@ export async function buildTranslitShard(
|
|
|
164
169
|
): Promise<void> {
|
|
165
170
|
const corpusVersion = options.corpusVersion ?? "0.4.0"
|
|
166
171
|
const canonicalPathPrefix = options.canonicalPathPrefix ?? "/data/"
|
|
167
|
-
const legacyPathPrefix = options.legacyPathPrefix ??
|
|
172
|
+
const legacyPathPrefix = options.legacyPathPrefix ?? `${mailwomanDataRoot()}/`
|
|
168
173
|
|
|
169
174
|
if (!existsSync(options.jsonl)) throw new Error(`jsonl not found: ${options.jsonl}`)
|
|
170
175
|
|
|
@@ -179,7 +184,7 @@ export async function buildTranslitShard(
|
|
|
179
184
|
const quarantine: string[] = []
|
|
180
185
|
let totalIn = 0
|
|
181
186
|
|
|
182
|
-
for await (const raw of
|
|
187
|
+
for await (const raw of JSONSpliterator.fromAsync<Record<string, unknown>>(options.jsonl)) {
|
|
183
188
|
totalIn++
|
|
184
189
|
const canon = toCanonicalRow(raw, corpusVersion)
|
|
185
190
|
const result = alignRow(canon)
|
|
@@ -222,7 +227,7 @@ export async function buildTranslitShard(
|
|
|
222
227
|
// Compose final MANIFEST: rewrite base.shards paths from /mnt/playpen/... → /data/... and append
|
|
223
228
|
// the new translit shards. Kryptonite shard already lives in the base manifest (it was written
|
|
224
229
|
// there by Thread B).
|
|
225
|
-
const base =
|
|
230
|
+
const base = parseJSONStrict<ShardManifest>(readFileSync(options.baseManifest, "utf8"))
|
|
226
231
|
|
|
227
232
|
const rewrittenBase = base.shards.map((sh) => ({
|
|
228
233
|
...sh,
|