Ericu950 commited on
Commit
8ce648b
·
verified ·
1 Parent(s): 5952424

DBBE fetch script, refreshed artifact index, de-anonymized metadata

Browse files
.pytest_cache/.gitignore ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ # Created by pytest automatically.
2
+ *
.pytest_cache/CACHEDIR.TAG ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ Signature: 8a477f597d28d172789f06886806bc55
2
+ # This file is a cache directory tag created by pytest.
3
+ # For information about cache directory tags, see:
4
+ # https://bford.info/cachedir/spec.html
.pytest_cache/README.md ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ # pytest cache directory #
2
+
3
+ This directory contains data from the pytest's cache plugin,
4
+ which provides the `--lf` and `--ff` options, as well as the `cache` fixture.
5
+
6
+ **Do not** commit this to version control.
7
+
8
+ See [the docs](https://docs.pytest.org/en/stable/how-to/cache.html) for more information.
.pytest_cache/v/cache/nodeids ADDED
@@ -0,0 +1,98 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [
2
+ "tests/test_data.py::test_conllu_roundtrip",
3
+ "tests/test_data.py::test_packing_invariants",
4
+ "tests/test_data.py::test_span_alignment",
5
+ "tests/test_data_meta.py::test_multitier_loader_yields_region_century_and_survives_windowing",
6
+ "tests/test_data_meta.py::test_shardset_defaults_unk_when_columns_absent",
7
+ "tests/test_data_meta.py::test_shardset_reads_region_century_columns",
8
+ "tests/test_edits.py::test_grave_fold",
9
+ "tests/test_edits.py::test_inapplicable",
10
+ "tests/test_edits.py::test_roundtrip[\\u03a3\\u03c9\\u03ba\\u03c1\\u03ac\\u03c4\\u03b7\\u03c2-\\u03a3\\u03c9\\u03ba\\u03c1\\u03ac\\u03c4\\u03b7\\u03c2]",
11
+ "tests/test_edits.py::test_roundtrip[\\u03b4\\u02bc-\\u03b4\\u03ad]",
12
+ "tests/test_edits.py::test_roundtrip[\\u03b8\\u03b5\\u03ce-\\u03b8\\u03b5\\u03cc\\u03c2]",
13
+ "tests/test_edits.py::test_roundtrip[\\u03ba\\u1f00\\u03b3\\u03ce-\\u03ba\\u1f00\\u03b3\\u03ce]",
14
+ "tests/test_edits.py::test_roundtrip[\\u03bc\\u03b1\\u03b8\\u03b7\\u03c4\\u1f74\\u03c2-\\u03bc\\u03b1\\u03b8\\u03b7\\u03c4\\u03ae\\u03c2]",
15
+ "tests/test_edits.py::test_roundtrip[\\u03bf\\u1f50\\u03ba-\\u03bf\\u1f50]",
16
+ "tests/test_edits.py::test_roundtrip[\\u03c4\\u03b9\\u03bb\\u03bb\\u03cc\\u03bc\\u03b5\\u03bd\\u03bf\\u03c2-\\u03c4\\u03af\\u03bb\\u03bb\\u03c9]",
17
+ "tests/test_edits.py::test_roundtrip[\\u1f00\\u03bd\\u03b8\\u03c1\\u03ce\\u03c0\\u03bf\\u03c5-\\u1f04\\u03bd\\u03b8\\u03c1\\u03c9\\u03c0\\u03bf\\u03c2]",
18
+ "tests/test_edits.py::test_roundtrip[\\u1f20\\u03bd\\u03b5\\u03af\\u03c7\\u03b5\\u03c4\\u03bf-\\u1f00\\u03bd\\u03ad\\u03c7\\u03c9]",
19
+ "tests/test_hf_release_smoke.py::test_decode_boundaries_keeps_punctuation",
20
+ "tests/test_hf_release_smoke.py::test_decode_diacritics_keeps_punctuation_and_boundary",
21
+ "tests/test_hf_release_smoke.py::test_decode_restoration_fills_masked_gap_and_keeps_punctuation",
22
+ "tests/test_hf_release_smoke.py::test_decode_restoration_handles_fully_masked_dia_and_boundary",
23
+ "tests/test_hf_release_smoke.py::test_processor_round_trips_punctuation_with_no_masking",
24
+ "tests/test_hf_release_smoke.py::test_restore_elastic_sweeps_widths_and_ranks_by_confidence",
25
+ "tests/test_hf_release_smoke.py::test_tiny_model_forward_pass_shapes",
26
+ "tests/test_insc_eval_whole.py::test_eval_span_whole_never_overlaps_real_lacuna",
27
+ "tests/test_insc_eval_whole.py::test_eval_span_whole_runs_end_to_end",
28
+ "tests/test_insc_eval_whole.py::test_eval_span_whole_skips_records_with_no_valid_position",
29
+ "tests/test_insc_lacuna.py::test_iphi_cap_plane_captured_not_discarded",
30
+ "tests/test_insc_lacuna.py::test_iphi_known_length_dash_run_marked_real_lacuna",
31
+ "tests/test_insc_lacuna.py::test_iphi_no_lacuna_when_no_dash",
32
+ "tests/test_insc_lacuna.py::test_papyri_dash_run_exact_length",
33
+ "tests/test_insc_lacuna.py::test_papyri_ellipsis_gets_random_stand_in_width_in_range",
34
+ "tests/test_insc_lacuna.py::test_papyri_mixed_dash_and_ellipsis_in_one_text",
35
+ "tests/test_insc_smoke.py::test_restoration_forward_pass_with_masked_gap",
36
+ "tests/test_marks.py::test_ambiguous_mask_matches_reference",
37
+ "tests/test_marks.py::test_bracketize_roundtrip",
38
+ "tests/test_marks.py::test_concat_verses_boundaries",
39
+ "tests/test_marks.py::test_enforce_circumflex_heavy_leaves_non_circumflex_alone",
40
+ "tests/test_marks.py::test_enforce_circumflex_heavy_overrides_light",
41
+ "tests/test_marks.py::test_insert_marks_roundtrip",
42
+ "tests/test_marks.py::test_macron_roundtrip_against_plain_column",
43
+ "tests/test_marks.py::test_merge_vowelless_syllables",
44
+ "tests/test_marks.py::test_merge_vowelless_syllables_leaves_real_syllables_alone",
45
+ "tests/test_marks.py::test_norma_lines_parse",
46
+ "tests/test_marks.py::test_parse_macron_combining_marks",
47
+ "tests/test_marks.py::test_parse_macron_simple",
48
+ "tests/test_marks.py::test_parse_scan_simple",
49
+ "tests/test_marks.py::test_scan_corpus_lines_encode",
50
+ "tests/test_meta_conditioning.py::test_collate_propagates_region_century",
51
+ "tests/test_meta_conditioning.py::test_collate_unk_constants_match_meta_vocab",
52
+ "tests/test_meta_conditioning.py::test_data_py_unk_constants_match_meta_vocab",
53
+ "tests/test_meta_conditioning.py::test_model_ignores_region_century_when_disabled",
54
+ "tests/test_meta_conditioning.py::test_pack_batch_propagates_region_century_and_defaults_unk",
55
+ "tests/test_meta_conditioning.py::test_parse_year_rejects_sentinels_and_junk",
56
+ "tests/test_meta_conditioning.py::test_record_century_id_midpoint_and_fallback",
57
+ "tests/test_meta_conditioning.py::test_region_to_id_known_and_unknown",
58
+ "tests/test_meter_hf_release_smoke.py::test_ambiguous_mask_and_mark_insertion",
59
+ "tests/test_meter_hf_release_smoke.py::test_bracketize_basic",
60
+ "tests/test_meter_hf_release_smoke.py::test_convert_checkpoint_to_hf_meter_kind",
61
+ "tests/test_meter_hf_release_smoke.py::test_meter_model_forward_and_decode_shapes",
62
+ "tests/test_noising.py::test_channel_dropout_frequencies",
63
+ "tests/test_noising.py::test_channel_masks_are_genuinely_patchy_sometimes",
64
+ "tests/test_noising.py::test_elastic_preserves_visible_chars_and_lengths",
65
+ "tests/test_noising.py::test_halfword_pattern_only_masks_within_single_words",
66
+ "tests/test_noising.py::test_halfword_span_always_within_the_word_and_hits_all_anchors",
67
+ "tests/test_noising.py::test_iid_rate_matches_t",
68
+ "tests/test_noising.py::test_is_real_lacuna_none_is_fully_backward_compatible",
69
+ "tests/test_noising.py::test_labels_only_at_masked_positions_fixed_patterns",
70
+ "tests/test_noising.py::test_mixture_covers_all_patterns",
71
+ "tests/test_noising.py::test_real_lacuna_excluded_from_elastic_rebuild_too",
72
+ "tests/test_noising.py::test_real_lacuna_never_selected_as_synthetic_target_fixed_patterns",
73
+ "tests/test_noising.py::test_substitute_never_uses_mask_token_and_label_is_true_char",
74
+ "tests/test_noising.py::test_word_masking_respects_boundaries",
75
+ "tests/test_normalize.py::test_capital_with_breathing",
76
+ "tests/test_normalize.py::test_dia_pack_unpack_roundtrip",
77
+ "tests/test_normalize.py::test_drops_nongreek_record",
78
+ "tests/test_normalize.py::test_elision_apostrophe_is_word_boundary",
79
+ "tests/test_normalize.py::test_final_and_lunate_sigma_merge",
80
+ "tests/test_normalize.py::test_iota_subscript_and_diaeresis",
81
+ "tests/test_normalize.py::test_kai_ho_logos",
82
+ "tests/test_normalize.py::test_punct_without_space_is_sentence_final",
83
+ "tests/test_normalize.py::test_restore_polytonic_simple",
84
+ "tests/test_normalize.py::test_roundtrip_on_real_records[pristine/pristine-00000.parquet]",
85
+ "tests/test_normalize.py::test_roundtrip_on_real_records[repaired/repaired-00000.parquet]",
86
+ "tests/test_packing.py::test_pack_batch_metadata_dropout_default_off_matches_prior_behavior",
87
+ "tests/test_packing.py::test_pack_batch_metadata_dropout_forces_unk_sometimes",
88
+ "tests/test_packing.py::test_pack_batch_real_lacuna_never_supervised",
89
+ "tests/test_packing.py::test_packing_labels_only_at_masked",
90
+ "tests/test_packing.py::test_packing_no_doc_crossing_and_fill",
91
+ "tests/test_syntax_smoke.py::test_joint_model_forward_pass",
92
+ "tests/test_ud_conversion.py::test_auxp_promotes_nominal_to_obl",
93
+ "tests/test_ud_conversion.py::test_coord_promotes_first_conjunct_with_generic_fallback",
94
+ "tests/test_ud_conversion.py::test_empty_sentence",
95
+ "tests/test_ud_conversion.py::test_pnom_copula_reversal",
96
+ "tests/test_ud_conversion.py::test_simple_sentence_sbj_pred_atr",
97
+ "tests/test_ud_conversion.py::test_xpos_to_upos_feats_verb_and_noun"
98
+ ]
MODEL_CARDS_INDEX.md CHANGED
@@ -27,18 +27,17 @@ All built on `Stoicheia-doc_clean`.
27
  | [`Ericu950/Stoicheia-meter`](https://huggingface.co/Ericu950/Stoicheia-meter) | macronization (vowel length) + metrical scansion, trained jointly |
28
  | [`Ericu950/Stoicheia-macronizer`](https://huggingface.co/Ericu950/Stoicheia-macronizer) | macronization only — the arm the paper's macronization ablation is measured on |
29
 
30
- Not released as a model: the authorship-attribution / hermeneutic probes, which are
31
- designed to be fine-tuned per-task rather than distributed as a general-purpose model
32
- (see the paper's Discussion section).
33
-
34
  ## Datasets
35
 
36
  | dataset | what it is |
37
  |---|---|
38
- | [`Ericu950/AncientGreek`](https://huggingface.co/datasets/Ericu950/AncientGreek) | main pretraining corpus (gold + silver tiers) |
39
- | [`Ericu950/SyntheticAncientGreek-CorpusCorporum`](https://huggingface.co/datasets/Ericu950/SyntheticAncientGreek-CorpusCorporum) | bronze synthetic augmentation tier |
40
  | [`Ericu950/Inscriptions_2`](https://huggingface.co/datasets/Ericu950/Inscriptions_2) | PHI inscriptions used for the 10-fold split and restoration fine-tuning |
 
 
41
 
42
- Not re-released (already public elsewhere, cited not duplicated): the *Norma*
43
- macronization/scansion benchmark (GitHub), the OGA/AGDT treebank
44
- (Celano's own repository).
 
 
27
  | [`Ericu950/Stoicheia-meter`](https://huggingface.co/Ericu950/Stoicheia-meter) | macronization (vowel length) + metrical scansion, trained jointly |
28
  | [`Ericu950/Stoicheia-macronizer`](https://huggingface.co/Ericu950/Stoicheia-macronizer) | macronization only — the arm the paper's macronization ablation is measured on |
29
 
 
 
 
 
30
  ## Datasets
31
 
32
  | dataset | what it is |
33
  |---|---|
34
+ | [`Ericu950/AncientGreek`](https://huggingface.co/datasets/Ericu950/AncientGreek) | pretraining corpus, ~361M words in `pristine` and `repaired` tiers |
35
+ | [`Ericu950/SyntheticAncientGreek-CorpusCorporum`](https://huggingface.co/datasets/Ericu950/SyntheticAncientGreek-CorpusCorporum) | bronze synthetic augmentation tier, machine-translated from Latin |
36
  | [`Ericu950/Inscriptions_2`](https://huggingface.co/datasets/Ericu950/Inscriptions_2) | PHI inscriptions used for the 10-fold split and restoration fine-tuning |
37
+ | [`Ericu950/Stoicheia-meter-silver`](https://huggingface.co/datasets/Ericu950/Stoicheia-meter-silver) | silver macronization/scansion training data (Hypotactic-derived + constraint-solver mined) |
38
+ | [`Ericu950/norma`](https://huggingface.co/datasets/Ericu950/norma) | mirror of the *Norma Syllabarum Graecarum* benchmark, in the exact split the paper evaluates on |
39
 
40
+ `AncientGreek` omits the Database of Byzantine Book Epigrams, which is CC BY-NC-SA and so
41
+ cannot travel inside a CC BY-SA compilation; `scripts/fetch_dbbe.py` refetches it under its
42
+ own terms. Not re-released at all: the OGA/AGDT treebank splits, which come from Celano's
43
+ own repository and are cited rather than duplicated.
NOTICE CHANGED
@@ -1,5 +1,5 @@
1
  Stoicheia
2
- Copyright 2026 Anonymous (under review)
3
 
4
  This product is licensed under the Apache License, Version 2.0 (see LICENSE).
5
 
 
1
  Stoicheia
2
+ Copyright 2026 Eric Cullhed and Albin Thörn Cleland
3
 
4
  This product is licensed under the Apache License, Version 2.0 (see LICENSE).
5
 
README.md CHANGED
@@ -18,7 +18,7 @@ macronization and scansion). All model repos are public: weights ship as `model.
18
  loadable directly through `AutoModel.from_pretrained(..., trust_remote_code=True)`.
19
 
20
 
21
- [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/Ericu950/stoicheia/blob/main/Stoicheia_demo.ipynb)
22
 
23
  Run everything in the browser: [`Stoicheia_demo.ipynb`](Stoicheia_demo.ipynb) restores a lacuna of
24
  unknown width, picks the checkpoint that has provably never read your document, tags and parses a
@@ -75,6 +75,10 @@ decides where the words end.
75
  - `meter/` — macronization (vowel length) and metrical scansion, including the
76
  *Norma* benchmark protocol and rule-based silver-data mining pipeline.
77
  - `tests/` — CPU-only pytest suite.
 
 
 
 
78
 
79
  See [`REPRODUCING.md`](REPRODUCING.md) for the full environment setup and end-to-end
80
  reproduction walkthrough.
@@ -85,9 +89,11 @@ reproduction walkthrough.
85
  @misc{stoicheia2026,
86
  title = {Stoicheia: Character-Level Masked Diffusion for Ancient Greek Textual
87
  Restoration, Parsing, and Metrical Scansion},
88
- author = {Anonymous},
89
  year = {2026},
90
- note = {Under review; citation to be finalized on publication}
 
 
91
  }
92
  ```
93
 
 
18
  loadable directly through `AutoModel.from_pretrained(..., trust_remote_code=True)`.
19
 
20
 
21
+ [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/ericu9500/stoicheia/blob/main/Stoicheia_demo.ipynb)
22
 
23
  Run everything in the browser: [`Stoicheia_demo.ipynb`](Stoicheia_demo.ipynb) restores a lacuna of
24
  unknown width, picks the checkpoint that has provably never read your document, tags and parses a
 
75
  - `meter/` — macronization (vowel length) and metrical scansion, including the
76
  *Norma* benchmark protocol and rule-based silver-data mining pipeline.
77
  - `tests/` — CPU-only pytest suite.
78
+ - `scripts/fetch_dbbe.py` — refetches the Database of Byzantine Book Epigrams, which the
79
+ released corpus omits: DBBE is CC BY-NC-SA, whose non-commercial clause a CC BY-SA
80
+ compilation cannot carry. Run it to reconstruct the pretraining corpus exactly (5,476
81
+ records, ~0.2M words, 0.1% of the total); what you build then inherits DBBE's terms.
82
 
83
  See [`REPRODUCING.md`](REPRODUCING.md) for the full environment setup and end-to-end
84
  reproduction walkthrough.
 
89
  @misc{stoicheia2026,
90
  title = {Stoicheia: Character-Level Masked Diffusion for Ancient Greek Textual
91
  Restoration, Parsing, and Metrical Scansion},
92
+ author = {Cullhed, Eric and Th\"orn Cleland, Albin},
93
  year = {2026},
94
+ eprint = {2608.XXXXX},
95
+ archivePrefix = {arXiv},
96
+ primaryClass = {cs.CL}
97
  }
98
  ```
99
 
REPRODUCING.md CHANGED
@@ -3,7 +3,7 @@
3
  ## 1. Environment
4
 
5
  ```bash
6
- git clone https://github.com/Ericu950/stoicheia
7
  cd stoicheia
8
  export STOICHEIA_DATA=/path/to/a/writable/data+checkpoints/directory
9
  source env.sh
 
3
  ## 1. Environment
4
 
5
  ```bash
6
+ git clone https://github.com/ericu9500/stoicheia
7
  cd stoicheia
8
  export STOICHEIA_DATA=/path/to/a/writable/data+checkpoints/directory
9
  source env.sh
Stoicheia_demo.ipynb CHANGED
@@ -20,7 +20,7 @@
20
  "4. macronize and scan a line of verse\n",
21
  "5. score the macronizer against a hand-annotated benchmark\n",
22
  "\n",
23
- "Every model and dataset used below is public. Anonymous release accompanying a paper under review.\n"
24
  ]
25
  },
26
  {
 
20
  "4. macronize and scan a line of verse\n",
21
  "5. score the macronizer against a hand-annotated benchmark\n",
22
  "\n",
23
+ "Every model and dataset used below is public.\n"
24
  ]
25
  },
26
  {
pyproject.toml CHANGED
@@ -10,7 +10,8 @@ readme = "README.md"
10
  requires-python = ">=3.10"
11
  license = { text = "Apache-2.0" }
12
  authors = [
13
- { name = "Anonymous (under review)" },
 
14
  ]
15
 
16
  [tool.setuptools]
 
10
  requires-python = ">=3.10"
11
  license = { text = "Apache-2.0" }
12
  authors = [
13
+ { name = "Eric Cullhed" },
14
+ { name = "Albin Thörn Cleland" },
15
  ]
16
 
17
  [tool.setuptools]
scripts/build_notebook.py CHANGED
@@ -1,5 +1,5 @@
1
  #!/usr/bin/env python3
2
- """Build the Stoicheia demo notebook (anonymous, Colab-ready)."""
3
  import json, pathlib
4
 
5
  ORG = "Ericu950"
@@ -25,7 +25,7 @@ cells = [
25
  "4. macronize and scan a line of verse",
26
  "5. score the macronizer against a hand-annotated benchmark",
27
  "",
28
- "Every model and dataset used below is public. Anonymous release accompanying a paper under review."),
29
 
30
  code("%pip install -q --upgrade transformers huggingface_hub safetensors torch datasets"),
31
 
 
1
  #!/usr/bin/env python3
2
+ """Build the Stoicheia demo notebook (Colab-ready)."""
3
  import json, pathlib
4
 
5
  ORG = "Ericu950"
 
25
  "4. macronize and scan a line of verse",
26
  "5. score the macronizer against a hand-annotated benchmark",
27
  "",
28
+ "Every model and dataset used below is public."),
29
 
30
  code("%pip install -q --upgrade transformers huggingface_hub safetensors torch datasets"),
31
 
scripts/fetch_dbbe.py ADDED
@@ -0,0 +1,74 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """Fetch the Database of Byzantine Book Epigrams, which the corpus release omits.
3
+
4
+ DBBE is distributed under CC BY-NC-SA. The pretraining corpus is a CC BY-SA compilation,
5
+ and a non-commercial clause cannot be honoured inside one, so the 5,476 DBBE records
6
+ (~0.2M words, 0.1% of the corpus) are excluded from the released dataset even though they
7
+ were present in the corpus we pretrained on. This script rebuilds that corpus exactly, on
8
+ the reader's own terms rather than ours.
9
+
10
+ python scripts/fetch_dbbe.py --out dbbe.jsonl
11
+
12
+ Anything you build from the result inherits DBBE's non-commercial and share-alike terms.
13
+ See https://www.dbbe.ugent.be for the licence and citation policy.
14
+ """
15
+ import argparse
16
+ import json
17
+ import time
18
+ import urllib.request
19
+ from pathlib import Path
20
+
21
+ API = "https://www.dbbe.ugent.be/api/occurrences"
22
+ UA = {"User-Agent": "stoicheia-corpus-rebuild/1.0 (+https://github.com/ericu9500/stoicheia)"}
23
+
24
+
25
+ def fetch_page(page, per_page, retries=3):
26
+ url = f"{API}?page={page}&limit={per_page}"
27
+ for attempt in range(retries):
28
+ try:
29
+ req = urllib.request.Request(url, headers=UA)
30
+ with urllib.request.urlopen(req, timeout=60) as r:
31
+ return json.loads(r.read().decode("utf-8"))
32
+ except Exception as e: # noqa: BLE001
33
+ if attempt == retries - 1:
34
+ raise
35
+ print(f" retry {attempt + 1} after {type(e).__name__}", flush=True)
36
+ time.sleep(5 * (attempt + 1))
37
+
38
+
39
+ def main():
40
+ ap = argparse.ArgumentParser()
41
+ ap.add_argument("--out", default="dbbe.jsonl")
42
+ ap.add_argument("--per-page", type=int, default=100)
43
+ ap.add_argument("--max-pages", type=int, default=0, help="0 = until exhausted")
44
+ a = ap.parse_args()
45
+
46
+ out = Path(a.out)
47
+ n = page = 0
48
+ with out.open("w", encoding="utf-8") as fh:
49
+ while True:
50
+ page += 1
51
+ if a.max_pages and page > a.max_pages:
52
+ break
53
+ data = fetch_page(page, a.per_page)
54
+ items = data.get("data") or data.get("items") or []
55
+ if not items:
56
+ break
57
+ for it in items:
58
+ text = (it.get("text") or "").strip()
59
+ if not text:
60
+ continue
61
+ fh.write(json.dumps({"source": "dbbe",
62
+ "id": str(it.get("id", "")),
63
+ "license": "CC BY-NC-SA",
64
+ "text": text}, ensure_ascii=False) + "\n")
65
+ n += 1
66
+ print(f" page {page}: {n} records so far", flush=True)
67
+ time.sleep(1) # be polite to the API
68
+
69
+ print(f"wrote {n} records to {out}")
70
+ print("These records are CC BY-NC-SA: anything you merge them into inherits those terms.")
71
+
72
+
73
+ if __name__ == "__main__":
74
+ main()