File size: 7,163 Bytes
5952424
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
#!/usr/bin/env python3
"""Stage 9: per-fold manifests of the TEST split.

For every fold writes:
  fold_k/test_manifest.tsv  one row per test record:
      id, kind, source, group, author_title, nchars, preview
  fold_k/test_works.tsv     aggregated by group (volume/work/TM/PHI):
      group, kind, source, records, chars, author_title, preview
and TEST_MANIFEST.md at the root with per-fold summaries.

"group" is the same work/volume granularity the pipeline used for splitting:
oga tlgXXXX.tlgYYY, catholic/greek_pd/ia/pg/gutenberg volume prefix,
TM:<number> for papyri, PHI:<id> for inscriptions. Texts listed here are
guaranteed (see verify_report) to share no 8-word sequence and no sentence
with the fold's train (or val) set -- safe for reconstruction evaluation.
"""
import io
import os
import re
import sys
from collections import defaultdict

import orjson
import zstandard as zstd

sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))

ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
OUTDIR = os.path.expandvars(os.environ.get("FOLD_OUTDIR", "$STOICHEIA_DATA"))
OGA_XML = os.path.expandvars("$STOICHEIA_DATA/raw/oga/"
           "opera_graeca_adnotata_v0.2.0/work_chronology/texts/"
           "chronology_greek_works.xml")
DDBDP_JSONL = os.path.expandvars("$STOICHEIA_DATA/clean/ddbdp.jsonl")
PAPYRI_TM_JSONL = os.path.expandvars("$STOICHEIA_DATA/data/papyri_clean.jsonl")


def load_oga_names():
    names = {}
    if not os.path.exists(OGA_XML):
        return names
    txt = io.open(OGA_XML, encoding="utf-8").read()
    for m in re.finditer(r"<record>(.*?)</record>", txt, re.S):
        blk = m.group(1)
        def g(tag):
            mm = re.search("<%s>(.*?)</%s>" % (tag, tag), blk, re.S)
            return mm.group(1).strip() if mm else ""
        urn = g("urn_cts")
        if urn:
            names[urn] = "%s — %s" % (g("author"), g("title_labels"))
    return names


def load_ddbdp_tm():
    tm_by_base = {}
    with open(PAPYRI_TM_JSONL, "rb") as f:
        for line in f:
            r = orjson.loads(line)
            tm_by_base[r["file"]] = str(r["TM"])
    id2tm = {}
    with open(DDBDP_JSONL, "rb") as f:
        for line in f:
            r = orjson.loads(line)
            tm = tm_by_base.get(r["file"].rsplit("/", 1)[-1])
            if tm:
                id2tm[r["id"]] = tm
    return id2tm


def read_zst(path):
    dctx = zstd.ZstdDecompressor()
    with open(path, "rb") as f:
        with dctx.stream_reader(f) as r:
            for line in io.TextIOWrapper(r, encoding="utf-8"):
                if line.strip():
                    yield orjson.loads(line)


def clean(s, n=95):
    return re.sub(r"\s+", " ", s or "").strip()[:n]


def main():
    oga = load_oga_names()
    id2tm = load_ddbdp_tm()
    print("oga names: %d, ddbdp->TM: %d" % (len(oga), len(id2tm)))

    md = ["# Test-split manifests", "",
          "One row per test record in `fold_k/test_manifest.tsv`; aggregated",
          "by work/volume/document in `fold_k/test_works.tsv`.",
          "Every text listed is verified to share no 8-word sequence and no",
          "complete sentence with that fold's train and val sets (after",
          "orthographic normalization) — safe targets for text-reconstruction",
          "evaluation of a model trained on the same fold's train set.", ""]

    for k in range(10):
        d = os.path.join(OUTDIR, "fold_%d" % k)
        rows = []
        for r in read_zst(os.path.join(d, "test.jsonl.zst")):
            rid = r["id"]
            src = r.get("source", "")
            if r.get("tier") == "inscriptions":
                kind = "inscription"
                group = "PHI:%s" % r.get("PHI_ID")
                name = clean(r.get("main_region", "") or "", 40)
                text = r.get("with_diacritics") or r.get("edition") or \
                    r.get("ithaca_text") or ""
            else:
                text = r.get("text", "")
                if src == "dclp":
                    kind = "papyrus"
                    group = "TM:" + rid.split("_")[0]
                    name = ""
                elif src == "ddbdp":
                    kind = "papyrus"
                    tm = id2tm.get(rid)
                    group = "TM:%s" % tm if tm else "ddbdp:" + rid
                    name = rid
                else:
                    kind = "literary"
                    if src == "oga":
                        group = ".".join(rid.split(".")[:2])
                        name = oga.get(group, "")
                    elif "#" in rid:
                        group = rid.split("#")[0]
                        name = group if src == "catholic" else ""
                    else:
                        group = rid
                        name = ""
                    group = src + ":" + group
            rows.append((rid, kind, src, group, name, len(text), clean(text)))

        with io.open(os.path.join(d, "test_manifest.tsv"), "w",
                     encoding="utf-8") as f:
            f.write("id\tkind\tsource\tgroup\tauthor_title\tnchars\tpreview\n")
            for row in rows:
                f.write("\t".join(str(x) for x in row) + "\n")

        groups = defaultdict(lambda: [0, 0, "", "", "", ""])
        for rid, kind, src, group, name, nch, prev in rows:
            g = groups[group]
            g[0] += 1
            g[1] += nch
            if nch >= len(g[5]):
                g[2], g[3], g[4], g[5] = kind, src, name or g[4], prev
            elif name and not g[4]:
                g[4] = name
        with io.open(os.path.join(d, "test_works.tsv"), "w",
                     encoding="utf-8") as f:
            f.write("group\tkind\tsource\trecords\tchars\tauthor_title\tpreview\n")
            for group, g in sorted(groups.items(), key=lambda x: -x[1][1]):
                f.write("%s\t%s\t%s\t%d\t%d\t%s\t%s\n" %
                        (group, g[2], g[3], g[0], g[1], g[4], g[5]))

        by_kind = defaultdict(lambda: [0, 0])
        for _, kind, *_rest in rows:
            pass
        for rid, kind, src, group, name, nch, prev in rows:
            by_kind[kind][0] += 1
            by_kind[kind][1] += nch
        ngroups = len(groups)
        md += ["## fold %d — %d records, %d works/documents" %
               (k, len(rows), ngroups), ""]
        md += ["| kind | records | Mchars |", "|---|---|---|"]
        for kind in ("literary", "papyrus", "inscription"):
            c = by_kind.get(kind, [0, 0])
            md.append("| %s | %d | %.1f |" % (kind, c[0], c[1] / 1e6))
        md += ["", "Largest test works:", ""]
        top = sorted(groups.items(), key=lambda x: -x[1][1])[:12]
        for group, g in top:
            label = g[4] or g[5][:60]
            md.append("- `%s` (%d recs, %.2f Mchars) %s" %
                      (group, g[0], g[1] / 1e6, label))
        md.append("")
        print("fold %d: %d records, %d groups" % (k, len(rows), ngroups))

    with io.open(os.path.join(OUTDIR, "TEST_MANIFEST.md"), "w",
                 encoding="utf-8") as f:
        f.write("\n".join(md))
    print("wrote", os.path.join(OUTDIR, "TEST_MANIFEST.md"))


if __name__ == "__main__":
    main()