Working files live in tmp/lxx/ (git-ignored): compare.py (harness, dual-pass:
accented + accent-insensitive), report.txt, misses.tsv, mismatches.tsv,
common_misses.tsv.
- Misses: 5.15% of tokens — ~92% are proper names absent from the stemlib (Ισραηλ ×2956, Δαυιδ ×1091, Ιερουσαλημ ×867, …). Also real common-word gaps: whole verb εὐλογέω missing (only its compounds exist in lsj.vbs), noun σάββατον missing, plus scattered forms (λήμψεται, ἤλθοσαν, δῴη, ἐκέκραξα…).
- Mismatches: 4.54% — mostly harmless lemma-convention differences (the corpus lemmatizes compounds to the base verb), but includes real bugs documented below.
- Robustness bug: cruncher silently returns nothing for uppercase beta code
(
E)Nvse)n) — exactly how the reference corpus is encoded.
- 1. Add
scripts/build_lxx_stemlib.py(generator, mirrorsbuild_biblical_stemlib.py: idempotent via a managed-section marker +load_existing()merge;--include-recognizedfor full-coverage recovery). Reads the reference corpus's*.mlxxfiles, runs Morpheus over distinct forms, emitsstemlib/Greek/stemsrc/nom.lxxcontaining: - proper names the corpus marks as such (typeN/ capitalized lemma) that Morpheus misses → indeclinablepers_nameentries with attested case/number/gender from its parse codes; two-letter masc/fem names kept, two-letter neuter tokens dropped as artifacts; - curated missing common words: σάββατον, ταμιεῖον, βροῦχος (generative) and ἀμνός, ἀδελφιδός, τρυβλίον (explicit:wd:paradigms — the generative patterns do not produce these forms). - 2. Add base verbs εὐλογέω (
eu)loge/w) and ἐγγίζω (e)ggi/zw) tostemlib/Greek/stemsrc/lsj.vbsat their sorted positions. - 3. Fix uppercase-beta-code handling in
python/morpheus_toolkit/runner.py(_normalize_beta, Greek only — Latin is case-sensitive) + unit tests inpython/tests/test_runner.py. - 4. Rebuild stemlib via
scripts/build-stemlib.sh; verified: names resolve (Ἰσραηλ→Ἰσραήλ, Δαυιδ→Δαυίδ, Ωγ…), common words resolve (σαββάτων→σάββατον, ἀμνοῖς→ἀμνός, …), εὐλόγησεν/ἐγγίζει → correct lemmas, uppercaseE)N/QEO/Sanalyze through the toolkit. - 5. Test suite: 31 passed (29 pre-existing + 2 new).
- 6. Re-ran the reference-corpus comparison; metrics below.
- Perfect passive participles of -έω verbs are systematically missing
(εὐλογημένος, τιμημένος, δοξασμένος all miss). Generation-mechanism gap; no
precedent for explicit
:vb:participle lines in the vbs sources. - Other missing verb inflections: future middle of λαμβάνω (λήμψεται…), aorist 3pl -οσαν/-σαν (ἤλθοσαν, εἴδοσαν…), optative δῴη, perfect active ἐκέκραξα.
-πλlemma artifact:addconstraints.plauto-generates plural entities for pers_names, but singular forms get attributed to the plural entity (βορρᾶ → "Βορέασ-πλ"). Needs build-system investigation.- Compound-decomposition bug: ἀπηγγέλη → "ἀπό-ἐγγελάω" (should be ἀπο-ἀγγέλλω). C-level scanner issue.
Before → after (LXX reference corpus, 623,685 tokens / 48,995 distinct forms):
| metric | before | after |
|---|---|---|
| analyzed (distinct forms) | 35,979 | 40,827 (+4,848) |
| misses | 5,574 forms / 32,102 tokens (5.15%) | 864 forms / 1,966 tokens (0.32%) |
| lemma mismatches | 7,442 / 28,336 (4.54%) | 7,304 / 27,554 (4.42%) |
Missed-token rate down 94%; the remaining misses are exactly the documented follow-up items (verb-inflection gaps, interjections). Mismatches barely moved — they are dominated by the corpus's base-verb lemma convention for compounds, which is not a Morpheus defect.
Verification performed:
pytest python/tests→ 31 passed (includes new uppercase-beta tests).- Target forms verified through the public API after rebuild (names in both
default and
ignore_accents=Truemodes; common words; εὐλογέω/ἐγγίζω forms; corpus-style uppercase input via the runner). - Generator idempotency proven: consecutive runs emit identical counts
(4,521 names / 5,672 forms);
--include-recognizedrecovers full coverage.
Notes for downstream users: unaccented biblical text (verse-initial capitals,
Rahlfs-style all-caps names) is best analyzed with Morpheus(ignore_accents=True);
the new name entries match in that mode regardless of accenting.
:vb:mechanism confirmed end-to-end.gensynform.cparses:vb:<form> <keys>viaScanAsciiKeys(key vocabulary insrc/morphlib/morphkeys.h+stemlib/Greek/rule_files/stemtypes.table) andGenIrregForm(genwd.c:217): the first field is stored verbatim as the surface form; keys must include a stemtype plus tense/mood/voice/person/number. A leading-disables a line (falls into the no-opproc_betabranch). Verb pipeline: vbs.simp.ml → conjfile → do_conj (GenConjForms, passes:vb:through) → conjfile.short → indexvbs → vbind, and the analyzer resolves explicit forms via vbind (proven: existing:vb:labe/analyzes, absent from binary endtables). So new:vb:lines in vbs.simp.ml need no makefile change.- Encoding: one simplified beta code everywhere (u=υ, y=ψ, f=φ, x=χ,
c=ξ, q=θ); the reference corpus differs only by case. Use the toolkit's
to_beta()to write surface forms — do not hand-transliterate. - λαμβάνω future middle: source declares
;fu mid, but generation emits a broken stem (lhy= ληψ-, μ dropped) and an n_infix/ionic one (lamy) that produces no endtable forms. Generative path is dead for this shape → explicit lines. - Compounds: no separate compound lemmas exist; the analyzer splits preverbs itself (ἐξῆλθεν → lemma e)ce/rxomai). Fixing the simplex form should cover compounds — to be verified empirically after rebuild.
- -έω perfect passive participles: τιμάω declares
;ppand τιμημένος now hits (Round-1 rebuild fixed it); εὐλογέω declares no;pp→ εὐλογημένος misses. Total perfp_p miss is only 66 tokens, dominated by εὐλογέω (55) → generative fix. - δίδωμι has no optative declaration and o_stem has no optative ending table (endtables/basics: only a1/ihn/imen/imhn/pr have opt tables) → explicit lines.
- κράζω: the corpus codes ἐκέκραξα etc. as reduplicated aorists (AAI), not the declared perfect stem κεκράγ- → explicit lines with aorist keys.
- ὁράω declares no active aorist at all (εἶδον resolves via the separate οἶδα lemma — a mismatch, not a miss) → explicit lines for the -οσαν/-αν variants.
- R2.1 εὐλογέω — done via explicit lines, not declarations. The planned
;pp/;pf/@ middeclarations are inert (they never reach vbind — see Review findings); 8 explicit:vb:lines with the corpus's exact surface spellings instead. εὐλογέω now 60/60. - R2.2 λαμβάνω — 19 explicit
:vb:lines (future middle incl. λημψόμεθα, aorist passive incl. both spellings of λημφθήτω, aorist active -οσαν/-αν variants, imperfect middle) + participle neuter acc sg. Coverage 324→440/454; residual = compound-passive forms under compound lemmas (out of scope). - R2.3 ἔρχομαι —
:vb:for ἤλθοσαν + ἐλθέτωσαν (aor2). Compound -θοσαν residuals remain (covered only via separate compound lemmas → mismatches, not misses; out of scope). - R2.4 δίδωμι — optatives δῴη/δῴης. Plan's
o_stemkey failed at runtime ("no stemtype seen": o_stem is derivtypes-only); fixed by adding real stemtypeomi_prbefore it, mirroring the existing:vb:di/dwprecedent. DI/DWMI now 34/34 covered. - R2.5 κράζω — 9 reduplicated-aorist
:vb:lines + augmented variant ἐκεκράξατε (the corpus attests both spellings). KRA/ZW now 60/60. - R2.6 ὁράω — 4 aorist-variant
:vb:lines. These fixed lemma attribution (forms previously resolved via οἶδα as mismatches; now also match gold ὁράω): lem-ok 5→46 of the covered forms. - R2.7 nom.lxx nouns — αλληλουία/ἀντιλήμπτωρ/νοσσία/παστοφόριον added, and
integrated into
scripts/build_lxx_stemlib.py(regeneration had wiped the hand-added section once; all curated entries now live in the script). νοσσία stored forms switched grave→acute per standword() semantics; generator gained astandardize()so no future run re-emits word-final graves (38 name entries fixed, 22 duplicate spellings deduped). - R2.8 Rebuild + verify: C harnesses (
test_lookup,test_indecl) then toolkit (verify_r2.py, dual accent modes);pytest python/tests→ 31 passed; fullcompare.pyre-run — metrics in Review below. - R2.9
lessons.mdupdated (pipeline truth, standword/CheckGenWords accent semantics, inert declarations, deriv redupl gate, managed-section trap).
- Interjections (οἴμοι ×18 …): Morpheus has no interjection POS; would require a category decision. ~44 tokens.
- C-level compound-decomposition bug (ἀπηγγέλη → ἀπό-ἐγγελάω) and the
-πλpers_name artifact from Round 1 — build-system issues, separate round. - The broken reg_conj future-middle generation for -μπ stems (produces ληψ- instead of λημψ-) — a C-code fix candidate for a later round; explicit lines cover the LXX need meanwhile.
- Round-3 candidates surfaced by this run's residual misses: compound passive/fut-passive forms under compound lemmas (συλλημφθήσῃ ×3 …, ~15 tok), ὁράω aorist/future passive (ὁραθῇ/ὁραθῆναι/ὁραθήσεται) + compounds (ἑώρακαν, ἀφιδὼν), and the new top misses: ἥκασιν ×20 (ἥκω), ἐνεπύρισαν/ἐνεπύρισεν ×31 (πυρίζω -οσαν aorist), γνώτωσαν ×10, εὕροσαν/ εὕροσάν ×15 (εὑρίσκω -οσαν), ἡμάρτοσαν/ἠγάγοσαν/ἐφάγοσαν/ἐνεβάλοσαν (-οσαν aorist pattern across several verbs).
- Reference-corpus lemma quirks, not Morpheus defects (now count as
mismatches):
ἀντιλήμπτωρ lemmatized without the μ (
A)NTILH/PTWR×19 tok); νοσσία accented on the final alpha (νοσσιά) instead of the standard penult (×17 tok); θιμωνιά same quirk (×4). Morpheus keeps the linguistically correct spellings.
Before → after (LXX reference corpus, 623,685 tokens / 48,995 distinct forms):
| metric | Round-1 build | Round-2 build |
|---|---|---|
| analyzed (distinct forms) | 40,827 | 40,939 (+112) |
| misses | 864 forms / 1,966 tokens (0.32%) | 729 forms / 1,155 tokens (0.19%) |
| lemma mismatches | 7,304 / 27,554 (4.42%) | 7,327 / 27,643 (4.43%) |
Missed-token rate down a further 41% (cumulative 96% vs Round-0's 5.15%). The small mismatch increase is expected: forms newly covered by this round include the corpus's quirky lemmas (above), which now count as mismatches instead of misses; ὁράω -οσαν forms conversely moved from mismatch (via οἶδα) to match.
Per-lemma results (tmp/lxx/verify_r2.py, dual accent modes):
| lemma | covered / total | notes |
|---|---|---|
| εὐλογέω | 60/60 ✓ | was 0/60; explicit :vb: lines (declarations are inert) |
| δίδωμι | 34/34 ✓ | was 0/34; omi_pr stemtype fix; 1 tok mismatch = παραδῴη compound convention |
| κράζω | 60/60 ✓ | was 0/60; reduplicated-aorist lines + augmented variant |
| νοσσία | 17/17 covered | grave→acute stored forms; lemma quirk (accent position) → mismatch, not miss |
| ἀντιλήμπτωρ | 19/19 covered | worked since Round-2 data landed; harness false-negative fixed; μ-dropped gold lemma → mismatch |
| αλληλουία / παστοφόριον | 24/24, 14/14 ✓ | unchanged from earlier verification |
| λαμβάνω | 324→440/454 | +116 tok; residual = compound-passive forms under compound lemmas |
| ἔρχομαι | 44/97 (unchanged) | simplex fixed in Round 1; compound -θοσαν covered only via compound lemmas → mismatches |
| ὁράω | 46/51 (coverage unchanged) | the 4 new lines fixed lemma attribution for 41 tok (οἶδα→ὁράω); residual = passive + compounds |
Root causes found and fixed this round:
;xx/@ xxparadigm declarations in vbs sources never reach the built index: makefile target iscat … > conjfile; do_conj; mv conjfile.short /tmp/vbmorph; indexvbs, and indexvbs only processes:vs/:aj/:no/:vb/:wd/:de:lines of the do_conj output. Only explicit lines (or:de:deriv stems) affect vbind.- The generative deriv path rejects non-reduplicated perfect stems
(
derivio.c checkcomderiv2:if (!had_redupl && Is_perfect(stemtype)) continue;), so -έω pp participles (εὐλογημένος) never generate. - GenIrregForm requires a real stemtype in keys —
o_stemalone is derivtypes-only and fails with "no stemtype seen" (δῴη);omi_prfixes it. standword()rewrites GRAVE→ACUTE on every input before lookup, and CheckGenWords compares accents exactly in normal mode — stored word-final graves can never match. Fixed at the root: generatorstandardize()+ νοσσία block; 38 name entries corrected, 22 duplicate spellings deduped (5672→5650 forms).- Managed-section trap: re-running build_lxx_stemlib.py rewrote nom.lxx from the script's lists and wiped the hand-added Round-2 section. All curated entries now live in the script; regeneration proven byte-identical for the generated section.
Verification performed:
- C harnesses against the rebuilt stemlib:
test_lookup(δῴη round-trip),test_indecl(all 5 νοσσία forms hit CheckGenWords in normal mode). - Toolkit dual-mode verification of every previously-missed target form
(
verify_r2.py, now reporting coverage vs lemma-agreement separately). pytest python/tests→ 31 passed.- Full corpus re-run:
tmp/lxx/compare.py(89 s) → report.txt metrics above; generator idempotency re-proven (two consecutive runs, generated section byte-identical).
Produce a complete, contextually disambiguated annotation of the whole Septuagint with
Morpheus — a drop-in alternative to the reference corpus's own *.mlxx annotations — and
measure how it compares. Rationale: after Rounds 1–2 Morpheus covers 99.81% of LXX tokens
(0.19% miss), so full-corpus annotation is now feasible; the toolkit already has the
disambiguation machinery (Viterbi POS-bigram tagger + frequency ranking, tagger.py /
ranking.py) and MorphGNT-style code mapping (morphgnt.py), built for the GNT —
Round 3
calibrates it to the LXX.
- Lemma convention: compound lemmas (Morpheus's natural output; more informative than
base-verb lemmatization). A
--base-lemmasmode is a possible follow-up but needs proper preverb handling — the toolkit does not expose the C-level preverb split — deferred. - Output format: same column layout as the reference corpus (
form type parse lemma [prefix]). The type column carries coarse POS (N, V, A, P, D, C, X, I, RA…); the parse column carries Morpheus's full 8-char parsing code (person/tense/voice/mood/case/number/ gender/degree) — a superset of the corpus's 5-char codes. Lemma in canonical beta, uppercased to match the corpus style. Prefix column empty in v1. - Disambiguation: Viterbi POS-bigram tagger over Morpheus candidates, with both models (POS bigrams + lemma frequencies) trained on the reference corpus itself (~624k tokens). Note: this calibrates to the domain but makes agreement-with-corpus evaluation partly circular; reported as such.
- Misses: fall back to the corpus's own annotation for the residual ~0.19% (interjections, rare forms) so the output is complete; fallbacks are counted and reported per book.
- R3.1 Train LXX models: both training scripts gained a
--format mlxxadapter (sharedcoarse_pos()mapper inmorphgnt.py: first letter, two-letter R-codes kept); emitteddata/pos_bigram_lxx.json(16 tags) +data/lemma_frequencies_lxx.json(12,943 lemmas / 623,685 tokens). Bundled GNT tables untouched. - R3.2 Driver
scripts/build_morph_lxx.py: reads all corpus*.mlxx; analyzes the 48,995 distinct forms viaMorpheus(ignore_accents=True)with an LXX ranker (joint lemma+POS frequencies); Viterbi-tags each contiguous token run; emits re-annotated files to--out-dirin the same column layout (headers preserved, lemmas uppercased to corpus style); fallback = corpus's own annotation for tokens with no candidate, recorded in<book>.fbsidecars + summary report. - R3.3 Evaluation
tmp/lxx/eval_morph.py: line-aligned comparison — lemma agreement (normalized), POS agreement, parse-field agreement (class-aware: verb codes are 5-char tense/voice/mood/person/number, nominal codes 3-char case/number/gender); Morpheus-only vs full-file rates; top disagreements ranked by corpus token frequency. - R3.4 Ran end-to-end on all 64 books (driver 66 s, eval 19 s) →
tmp/lxx/morph-lxx/; metrics in Review below;pytest python/tests→ 31 passed; lessons.md updated.
- Base-lemma output mode (needs preverb exposure/heuristics — follow-up).
- The known C-level compound-decomposition bug (ἀπηγγέλη → ἀπό-ἐγγελάω, ~26 tokens): it will show up in evaluation; fixing the scanner is a separate round.
- Interjection POS: Morpheus has no such category; those tokens use corpus fallback.
- Surfaced by this round's evaluation: article/pronoun dictionary-form conventions (the corpus groups all article forms under ὁ and lemmatizes pronouns to the nominative; Morpheus uses the stem ο( / distinct forms — ~40k tokens of the lemma gap); residual disambiguation errors (ὅτι→ὅστις ×4,044; θεοῦ→θεάομαι ×1,114; λέγων→λέγος ×814); accent/orthography differences on names and adverbs.
Deliverable: a complete Morpheus annotation of the whole Septuagint in the reference
corpus's own format — tmp/lxx/morph-lxx/ (64 re-annotated .mlxx files + summary.txt
- per-book fallback sidecars), produced by
scripts/build_morph_lxx.pyin 66 s.
Coverage: 623,685 tokens → 99.81% analyzed by Morpheus (622,530); the residual 1,155 tokens (0.19%) fall back to the corpus's own annotation, so the output is complete. Fallbacks are dominated by verb forms (VAI/VA/VBI/VC/V1) plus rare nouns — exactly the Round-2 residual list; interjections use fallback too (no POS in Morpheus).
Agreement with the reference corpus (Morpheus-only tokens, i.e. excluding fallbacks):
| dimension | agreement |
|---|---|
| lemma (normalized) | 90.01% (560,327/622,530) |
| POS (coarse) | 85.11% (529,808/622,530) |
| parse fields (class-aware; 421,191 comparable tokens) | 76.24% (321,123) |
Including fallback tokens: lemma 89.84%, POS 84.95%.
What the disagreement is made of (top items by token frequency):
- Dictionary-form conventions for articles/pronouns (~40k tokens): corpus groups article forms under ὁ and lemmatizes pronouns to the nominative; Morpheus returns the stem ο( or distinct forms. A convention difference, not an error — flip-able in a follow-up.
- Compound-verb lemma convention (base vs compound) — documented since Round 1.
- Accent/orthography: Δαυιδ→Δαυίδ ×1,091, πάντα→πᾶς ×1,378, ὅμοιος→ὁμοῖος…
- Genuine disambiguation errors the tagger still makes: ὅτι→ὅστις ×4,044, θεοῦ→θεάομαι ×1,114, λέγων→λέγος ×814, ἰδοὺ→ἰδού ×1,152.
- Cases where Morpheus is arguably more correct than the corpus: μοι → ἐγώ (corpus lemma "δς") ×964; standard accenting throughout.
Caveat: both models were trained on this same reference corpus, so agreement numbers measure domain consistency as much as absolute accuracy; they are a floor for real-world use on untagged LXX text, not a ceiling claim.
Verification performed:
pytest python/tests→ 31 passed (toolkit changes:coarse_pos()in morphgnt.py, format adapters in the two training scripts).- Output spot-checked by eye across registers (Gen 1:1, Psalms, Daniel OG) — annotations are linguistically sound; line counts match the source files exactly.
- Fallback sidecars sum to exactly the summary's fallback count (1,155).
The user grepped the working tree again and still saw the reference corpus's name in
runner.py "and other files". Rounds 1–3 had only scrubbed the docs; this round extends
it to every morpheus-owned file (committed + tmp). A word-boundary grep across all
file types found stragglers the earlier .py/.md/.sh-only verification missed: a stale
generated conjfile, old report/snapshot scratch files, and a local path embedded in
the Round-3 summary.
- R4.1
python/morpheus_toolkit/runner.py—_normalize_betadocstring no longer names the corpus ("some corpora (…)" → "some reference corpora"). - R4.2
python/tests/test_runner.py— same rewording in the test comment. - R4.3
tmp/lxx/compare.py— removed the hardcoded local checkout path; corpus location now comes from--corpus-dir/LXX_CORPUS_DIR(same interface as build_lxx_stemlib.py). - R4.4
stemlib/Greek/conjfile— stale generated artifact still carried the pre-scrub Round-2 comment wording; regenerated from the already-clean sources viascripts/build-stemlib.sh. Diff vs a pre-rebuild snapshot: exactly the 3 reworded comment lines, nothing else (stemlib otherwise byte-identical). - R4.5
scripts/build_morph_lxx.py— summary no longer embeds the local corpus path (it was leaking intomorph-lxx/summary.txt); now a neutral label. Regenerated all 64 outputs: every.mlxx+ sidecar md5-identical, only summary.txt changed. - R4.6 Deleted stale scratch snapshots retaining pre-scrub wording (report.prev, regen.diff, nom.lxx.{pre-scrub,pre-r2regen,post-r2regen}); re-ran compare.py so report.txt/TSVs come from the current clean format.
- Word-boundary grep for the corpus name across all repo files (excluding .venv): zero occurrences in any morpheus-owned file. The only remaining case-insensitive substring hits are legitimate Latin dictionary entries (peccator, siccatio, occatio…).
pytest python/tests→ 31 passed.- compare.py smoke run with the new CLI reproduced Round-2 metrics exactly: misses 729 forms / 1,155 tokens (0.19%), mismatches 4.43% — proving the interface change is behavior-neutral.
- Lessons captured in lessons.md (word-boundary all-file-type greps; never hardcode the corpus location).
The repo has no file named "tutorial" — the user-facing documentation is README.md
(overview, build, cruncher usage) and python/README.md (toolkit tutorial). Neither
mentions anything from Rounds 1–3: the LXX stemlib entries, uppercase-beta normalization,
or the full-LXX annotation pipeline. Update both.
- R5.1 Re-measured with the current build: AF coverage 90.9→91.2%, top-1 84.5→84.8% before / 89.9→90.1% after ranking, POS 74.6→74.3%; MorphGNT-sourced subset 96.4→96.5%, top-1 89.7→89.8% / 95.9%; analyze_corpus.py total 98.2→98.4%. SBLGNT row untouched (no local checkout).
- R5.2 Root
README.md: new "Septuagint (LXX)" section — what the stemlib gained (nom.lxxnames + curated common words, new verb entries), coverage result, regeneration commands; pointer to the toolkit docs for the annotation pipeline. - R5.3
python/README.md: intro bullet on case handling (Greek input is normalized to lowercase beta before the cruncher; Latin untouched); new "The Septuagint (LXX)" section — bundled LXX models + rebuild commands (--format mlxx),scripts/build_morph_lxx.pyfull-corpus annotation, agreement numbers with the circularity caveat. - R5.4 Refreshed the ranking table's AF rows (re-measured) with a footnote that the SBLGNT row predates the LXX additions; reworded the "Known limitations" coverage bullet (the old "~1.6%" figure was not reproducible from any local corpus).
- R5.5 Verified: documented commands match actual
--helpfor all four scripts; both READMEs contain zero occurrences of the scrubbed corpus name;pytest→ 31 passed.
Docs updated:
- README.md — new "Septuagint (LXX)" section between the Python-toolkit
blurb and Tests:
nom.lxxcontents (~4,500 names + curated common words), verb-source additions, 99.81% coverage result, regeneration commands; toolkit blurb now also points at LXX annotation support. - python/README.md — new "Case handling" intro bullet (uppercase-beta
normalization); new "The Septuagint (LXX)" section: bundled
*_lxx.jsonmodels + rebuild commands, full-corpus annotation viabuild_morph_lxx.py(format, fallback, lemma convention), agreement numbers with the circularity caveat; ranking table's AF rows re-measured and footnoted; coverage limitation bullet reworded.
Verification performed:
- Re-measurement ran the exact commands already documented in python/README.md against
../apostolic-fathers(63,222 tokens) with the Round-2-built stemlib — numbers above; movement is small but real (shared biblical names now resolve). - Every command shown in the docs was checked against the script's actual
--helpoutput (build_lxx_stemlib.py, build_morph_lxx.py, build_pos_model.py, build_lemma_frequencies.py) — flags and choices match. - Cross-links use valid anchors (
#septuagint-lxx,#the-septuagint-lxx). - Word-boundary grep of both READMEs: zero occurrences of the scrubbed corpus name (Round-4 constraint held).
pytest python/tests→ 31 passed.
The analyzer is at its stopping point for LXX (99.81% coverage; residual = interjections
- rare forms). Round 3's evaluation decomposed the ~10% lemma disagreement: dictionary-form
conventions (articles/pronouns, ~40k tokens) and compound-vs-base verb lemmas dominate;
genuine disambiguation errors are next. The significant gains live in the annotation layer.
This round adds two opt-in convention modes to
build_morph_lxx.pyand regenerates + re-evaluates.
- Opt-in flags; natural output stays default.
--dictionary-formsand--base-lemmas; with both off the Round-3 behavior is byte-identical. The "better morph lxx" run uses both. - Dictionary forms (MorphGNT-style). Article lemma ο( → ὁ. Pronoun mapping only where it is lossless and standard — decided per class from the corpus's own coding sheet: if the corpus uses catch-all lemmas (e.g. δς for personal pronouns) we do NOT copy that; those tokens stay as Morpheus's more informative lemmas and are documented as corpus quirks.
- Base lemmas. Strip preverbs from compound verb lemmas; a prefix is stripped only when the remainder resolves as a verb in Morpheus (guards against over-stripping).
- R6.1 Quantify: run
eval_morph.pyon the current output; count tokens per disagreement class (articles, pronoun pairs, compound-base pairs); read the corpus's*Morph-Codingsheet for its article/pronoun conventions. → Done in prior session: RA 88,429 tok (table covers 88,424), RP 26,934 (covers 26,628), RD 35,834 (covers 34,704); compounds ~17.5k head-strip + ~11.8k tail-match fixable (compound_counts{,2,3}.txt,tables.txt). - R6.2 Implement
--dictionary-formsinscripts/build_morph_lxx.py(+ mapping table). → Three closed-set tables (articles→ὁ/RA, personal pronouns→ἐγώ|σύ/RP, demonstratives→ family nominative/RD); unambiguous forms forced before Viterbi; 4 homographic article forms left contextual; type column emits the reference code when the reading matches. - R6.3 Implement
--base-lemmas(preverb list + verb-resolution guard). → Head preverb-stripping + tail-matching for contracted compounds, guarded on attested preverb remnants (PREVERB_REMNANTS, 40 LXX-attested ∪ full preverbs); memoized. - R6.4 Regenerate morph-lxx with both flags; re-run eval; before/after agreement table. → See Review below (all three metrics beat Round 3).
- R6.5 Update python/README.md LXX section (new flags, new numbers); lessons if any. → README updated (flags + mechanism + numbers table + residual breakdown); lessons.md extended with the beta-dialect / ranking / tagger lessons from this round.
Result: the flagged run beats Round 3 on all three metrics — no regressions.
| variant | lemma | POS | parse | Δ vs Round-3 |
|---|---|---|---|---|
| Round-3 natural (old ranking) | 90.01% | 85.11% | 76.24% | — |
natural, new ranking only (morph-lxx-base2) |
90.25% | 85.09% | 76.26% | +0.24 / −0.02 / +0.02 |
--dictionary-forms only (morph-lxx-dict) |
90.70% | 90.54% | 79.75% | +0.69 / +5.43 / +3.51 |
both flags (deliverable, morph-lxx/) |
94.23% | 90.54% | 79.75% | +4.22 / +5.43 / +3.51 |
(623,685 tokens; 622,530 Morpheus-analyzed; 1,155 fallbacks = 0.19%.)
The first flagged regeneration (prior session) regressed parse by −0.41pp. Line-by-line
attribution (diff_variants.py, with a monkey-patched reconstruction of the old ranking in
morph-lxx-oldnat/) isolated three distinct causes — all now fixed:
- Ranking over-promotion (root cause,
ranking.py). The joint-frequency lookup scored 0 when Morpheus's POS isn't a key in the lemma's row (αὐτός tagged A here but RD in the reference). The prior session's fix fell back to full plain lemma frequency, which over-credited unattested pairings: σός(N) 0.432 vs σύ(RP) 0.838 left only a ~1.2-nat emission gap that bigram context flipped (σοῦ/ἐμοῦ → possessive adjectives, τὰ/τοῦ → τίς). Fix: an unattested (lemma, POS) pair now scores a floor of one occurrence — it can never be the gold reading of any token (gold is built from the same reference), so the floor only removes wrong answers. Natural mode recovered fully (76.26% parse ≥ Round 3). - Forcing removed featureless candidates. Morpheus offers a surface-form pseudo-candidate for αὐτοῦ (lemma=αὐτοῦ, POS D, empty parse code) that vacuously passes parse comparison; forcing to hits[:1] deleted it and collapsed the real αὐτός candidates arbitrarily. Fix: forcing keeps all target-reading candidates so Viterbi still uses context.
- Same-POS feature ties are arbitrary in the tagger (one candidate per coarse POS, first
on score ties). For tied forms the reference's own accent notation correlates strongly with
the reading —
AU)TOU=is 97% GSM whileAU)TOU\S//Sare ~100% APM;AU)TW=Ncame out GPN though 92% of gold is GPM. Fix: a derivedTIE_BREAKtable (≥10 tokens, ≥90% majority per spelling; weaker cases like τὰ stay contextual) stable-sorts hits so the tie resolves toward the reference majority. This single change moved parse 75.79% → 79.75%.
Also verified: base-lemma mode is a pure lemma-column operation (+3.53pp lemma, zero POS/parse
effect); spot checks pass (ἀπέστειλεν→στέλλω+ΑΠΟ; contracted ἐκέρχομαι→ἔρχομαι at Gen 1:2368
matching gold exactly); summary.txt reports the active conventions; pytest 33 passed.
- Models are corpus-trained. Both disambiguation models come from this same reference, so agreement measures domain consistency as much as absolute accuracy — a floor for untagged LXX text, not a ceiling claim.
- The eval's "top lemma disagreements" list is misleading: it sorts by total form frequency and prints only the first disagreeing line. True per-form mismatch counts are far smaller (τοῦ: 1/10568; αὐτοῦ: 4/9181; οἱ: 1/4762; ὅτι: 8/4044 — the last was already ~right in Round 3, an earlier misread).
- Residual lemma disagreement (~5.8%) is dominated by documented reference quirks where Morpheus is arguably more correct (surface-form lemmas τοῦ→τοῦ; ἰδοὺ lemmatized as ὁράω ×28), the genuine ἕως/ἠώς homograph (277 tokens — largest true error), κυριόω intrusions (32), and ~286 compound verbs the reference keeps unstripped while the heuristic strips (ἐγείρω, ἐπίσταμαι, …; etymologically they are compounds). Not patched: copying per-lemma corpus choices would be overfitting to annotation inconsistency.
- Structural limit: the tagger keys states by coarse POS, so feature-level ambiguity within a POS (masc/neut homographs of article forms) is resolved only where the reference's accent notation carries signal; genuinely ambiguous cases stay at chance.
User direction: do NOT change anything NT-related; improve Morpheus against the
Apostolic Fathers corpus instead. All five approved items implemented, verified
and committed (d760a3e, 259df21, 23b33d3, b3707de). Working files in
tmp/af/ (git-ignored): evaluators (eval_af.py, eval_out.py), snapshots
(ev2_*.txt, ev3_*.txt), driver outputs, AF table sources.
- Verb mismatches on trustworthy gold are ~97% convention differences, not errors (reduplication γίγνομαι→γίνομαι ×249; augment ἐθέλω→θέλω ×95; aorist-vs-base εἶδον/εἶπον→ὁράω/λέγω ~160; accents ~116; act/mid ~90) — the Round-6 pattern.
- The "interrogative gap" was an accent artifact (MorphGNT writes τίς, HF lines τις): 42.6% → 0.2% under accent-insensitive comparison. No analyzer change needed.
- Elided forms (544 tokens, all HF-sourced) were a real analyzer gap: Morpheus returned no candidates for any of them.
- Joint ranking hurts lemma on AF (unlike LXX): the reference tags adjectives N where Morpheus says A; plain frequency is best for lemma, joint only for POS.
- 1. Commit the two toolkit fixes (frequency-builder guard for MorphGNT-format
corpora — previously all token lines skipped, 106 vs 5,874 lemmas; and
Analysis.matches_lemma()normalizing both sides). Verified: pytest 33/33, LXX rebuild byte-identical. (d760a3e) - 2. AF convention table + annotation pipeline —
scripts/build_morph_af.pymirroring the LXX driver (accent-insensitive mode, AF frequency ranking, optional Viterbi/joint). Convention layer derived at run time from MorphGNT-derived gold with three guards: single target per source lemma, ≥10 tokens, and M==0 — no trustworthy token with that choice may agree with the gold. The M==0 guard rejects form-dependent splits (κύριος/κυρία gender 10-vs-613; ἐγώ/κἀγώ enclitic 22-vs-1198; ἐπεί/ἐπειδή 17-vs-33) that a plain single-target guard would have applied globally and broken. Rejected pairs are printed in the summary for auditability. (23b33d3) - 3. Stemlib additions for elided forms —
stemsrc/nom.elided(13 forms: δι, κατ, παρ, μετ, ἀπ, ὑπ, ἐπ, ἀφ, ὑφ, ἐφ, καθ, μεθ, ἀλλ) mapping to existing full-form lemmas; rebuilt nomind. Entries resolve only in accent-insensitive mode (-n), so default-mode behavior is unchanged and vbind byte-identical. (259df21) - 4. Bundle AF tables + repo tooling —
lemma_frequencies_af.json/pos_bigram_af.jsoninpython/morpheus_toolkit/data/(neutral source label, LXX precedent);scripts/agreement_report.pygained--frequencies/--modelflags. (23b33d3) - 5. Upgrade the AF repo —
tools/morpheus/analyze.pyrewritten as a self-contained mirror (tables derived at run time from its own gold; conventions on by default with--no-conventionsescape); all 15data/morph-morpheus/*.txtregenerated + new.fbsidecars andsummary.txt; README rewritten. Committed locally in the AF repo (2b51d98, branchmorpheus-analysis) — not pushed.
Headline numbers (driver = accent-insensitive mode + AF frequency ranking + top-1 candidate + convention layer):
| subset | tokens | lemma | acc-insens lemma | POS | coverage |
|---|---|---|---|---|---|
| all Greek | 63,222 | 91.1% | 92.1% | 75.3% | 99.35% (413 fallbacks) |
source=MorphGNT (trustworthy) |
52,094 | 96.6% | 97.4% | 75.7% | 99.95% |
source=grc_proiel_lg (machine gold) |
11,128 | 65.4% | 67.2% | 73.1% | 96.5% |
- Baseline before the round: 90.4/74.3 (ALL), 96.3/75.5 (MG subset) with NT
tables, no
-n, ~1,027 misses. The convention layer lifts the trustworthy subset to 96.6% exact / 97.4% accent-insensitive;-nmode cuts misses from ~1,027 to 413 (elided forms + unaccented spellings). - Config trade measured: Viterbi (
--tagger) costs ~1.4pp MG-subset lemma for +1.0pp POS — the opposite of LXX, where it was net positive. Default is top-1 (lemma-first);--tagger/--jointavailable for POS-optimized output. - Verification: saved ev2 evaluator snapshots re-run byte-identical after all toolkit changes; pytest 33/33; AF repo's analyze.py output verified byte-identical to morpheus's build_morph_af.py across all 15 files; LXX driver spot-check clean. NT/LXX untouched (no shared stemlib lemma changes).
Open questions for the user: push morpheus origin/master (4 commits ahead of
75fface; the AF README pins 23b33d3, so pushing makes that pin real)? Push the
AF repo's morpheus-analysis branch?
User approved items 1–3 ("Do round 8"); item 4 (classical AGLDT) was explicitly
optional and is not done. All three complete and committed (a2b7a77 for item 1;
1935b8f for items 2–3). Working files in tmp/<corpus>/ (git-ignored):
PapyGreek treebanks + evaluators, PROIEL clone + evaluator, AGLDT clone + Latin
evaluator.
The Greek biblical side is near saturation: LXX 99.81% coverage (Round 2), NT ~95% top-1 after the name additions, AF 99.35% / 96.6–97.4% lemma (Round 7). Remaining levers found in corpus research (2026-10-11):
Key caveat — most Perseus "morph texts" are circular. Celano's OGA/OLA treebanks (45M Greek / 17M Latin words; the data behind morph.perseus.org) were auto-annotated with Morpheus (LemmatizedAncientGreekXML). They cannot serve as independent gold for improving Morpheus — at best a scale benchmark or POS-bigram source. The valuable resources are the manually curated treebanks and a few independent lemmatized corpora:
| corpus | size | annotation | license / access | improves |
|---|---|---|---|---|
PapyGreek Treebanks (Helsinki; Zenodo 10.5281/zenodo.5074307) |
44K tokens, documentary papyri ca. 300 BCE–700 CE (Zenon archive letters, petitions) | semi-manual + reviewed, dependency-morphosyntax; TEI XML | CC BY-SA 4.0, free | new Koine domain — first test of Morpheus on papyrological Greek; safe stemlib additions from human-reviewed gold |
| PROIEL greek-nt (Tischendorf 1869) | 140,763 tokens | fully manual morphosyntax + info structure; PROIEL XML and CoNLL-X | CC-BY-NC-SA 3.0, GitHub (syntacticus/syntacticus-treebank-data) |
independent second NT gold — validates how much of our SBLGNT-based NT numbers are robust vs convention-fitted (some sections unannotated: HEB 13, catholics) |
| PROIEL latin-nt (Jerome's Vulgate) + caes-gal/cic-att/per-aeth/pal-agr | ~113K tokens total | manual; CoNLL-X available | CC-BY-NC-SA 3.0, GitHub | first benchmark of the Latin analyzer (this checkout ships a full Latin stemlib — ~286K source lines — that has never been measured) |
AGLDT / Perseids v2.1 (PerseusDL/treebank_data) |
Greek ~190K words (Homer, Hesiod, Aeschylus, Sophocles, Thucydides 1, Aesop); Latin: Caesar, Cicero, Ovid, Petronius, Vergil… | manual by 200+ annotators; free on GitHub | open | classical register — expands/validates both languages beyond the biblical domain |
| Index Thomisticus (Busa) | 11M lemmatized words, medieval Latin | classic lemma+POS per word; plain text at corpus.thomisticum.org (+ treebank subset) | free | large-scale Latin frequency table source |
| OGA/OLA/GLAUx (Celano 2024) | 45M / 17M words | auto, Morpheus-derived → circular | open | not gold; scale benchmark only |
| TLG | huge | subscription; abridged untagged | paid | skip unless user has access |
| Hebrew (OSHB/SBLHBT) | — | — | free | N/A — this checkout has no Hebrew component |
No large freely available patristic tagged corpus exists (the AF repo remains the main one); PapyGreek is the closest new-domain gold in the user's register. (Note: AGLDT v2.1 Greek turned out much larger than this table — 557,918 words; see Round 9.)
Dataset: Zenodo 10.5281/zenodo.5074307 → tmp/papygreek/ezhenrik-papygreek-treebanks-b87127c/
(394 documentary + 1 literary ALDT v1.5 XML; 42,313 Greek tokens; CC BY-SA 4.0).
ALDT dual layers: input = form_orig (editorial markers stripped), gold =
lemma_reg/postag_reg. POSTAG is 9 chars — pos person number tense mood voice
GENDER CASE degree (gender before case, opposite of MorphGNT; lowercase).
Evaluator: tmp/papygreek/eval_pg.py (+ missdump.py) — coverage, top-1
before/after ranking (raw order via runner.analyze_beta(ignore_accents=True),
not the pre-ranked API), coarse POS, miss profile. The marker inventory was
expanded after a full corpus audit: beyond ∼∽∤⋰⋱⸢⸣❨❩<>[]{}() + U+0324 and
|num:N|/|g:…| spans, the corpus also uses 〚〛 (×310), ? (×81), ⊢⊣ (×50),
⸌⸍ (×37), ⎴⎵/⦑⦒ (×22), …, -, ⸜⸝, ⧼. The apostrophes ʼ ’ are real elision marks
(kept); combining diacritics ͂ ͅ are Greek letters (kept).
Baseline → after stemlib additions (36,455 word tokens; numerals tracked separately — Morpheus has no numeral support and alphabetic numerals collide with real words; documented limitation, not added to the stemlib):
| metric | baseline | after round-8 additions |
|---|---|---|
| coverage | 89.4% (old marker set) / 89.9% (fixed) | 92.9% |
| misses | 3,871 (marked 1,878; numerals 863/1,454) | 2,600 (marked 1,417; numerals 853/1,454) |
| top-1 lemma, NT tables | 78.3 / 80.0 exact/acc-insens | 81.2 / 83.3 |
| top-1 lemma, PG tables | 80.5 / 81.8 | 83.7 / 85.1 |
| POS coarse | ~80–82% | 81.1 (NT) / 81.7 (PG) |
Remaining misses are dominated by irreducible damage: 1,417 marked forms whose editorial reconstruction is partial, plus the 853 numeral tokens.
Stemlib additions (all additive; verified against the rebuilt indexes):
stemsrc/nom.papyri— generated by newscripts/build_papyri_stemlib.py(mirrors Round 2's builder: missed nominal tokens with capitalized gold lemma, count ≥ 2 threshold since papyri carry more scribal noise; dictionary-form recognition check; idempotent managed-section merge). 179 names / 407 forms — Egyptian deity/month/personal names (Θωθ, Ἰσις, Ἀμμώνους, Παῦνι, Ἐπείφ, Θερμουθᾶς, Τῦβι…), plus a curated section: papyrus dative-plural pronoun spellings ὑμεῖν→σύ / ἡμεῖν→ἐγώ and the Egyptian loanword κίκιος→κίκι. EXCLUDE: Ἀχιλλᾶς, Ἰωνᾶ (declined forms of known names used as lemmas — Morpheus already parses both correctly).stemsrc/vbs.papyri— hand-curated explicit:vb:entries for dialectal verb forms the generative paradigms don't produce: reduplicated aorists of λαμβάνω (ἰλήφασι ×15, ἔλαβα/ἐλαβα, εἴληφαν — standard Attic!, αἴλαβα), -εισκ-/-οσκ- presents and -ιν infinitives of γιγνώσκω, -ιν/-εριν infinitive variants of χαίρω. 15 forms / ~75 tokens. Makefile's vbind rule now lists the file (and gained explicit prerequisites — fixes the stale-target trap).stemsrc/nom.elided— added ἐγ→ἐκ (×16 in papyri) alongside Round 7's 13.
Bundled table: python/morpheus_toolkit/data/lemma_frequencies_pg.json
(3,913 lemmas; punctuation/junk filtered; source label "PapyGreek Treebanks
v1.01 (CC-BY-SA 4.0)" — bundling is license-fine, SBLGNT precedent). The PG
table beats the NT tables by +2.5pp exact top-1 on papyri (83.7 vs 81.2) —
domain tables matter for disambiguation, not just coverage.
Verification:
- Every new form resolves with its gold lemma (
analyze_beta, both modes); accented forms also hit in default mode; unaccented variants only under-n(CheckGenWords accent-exact gate — expected). - Index key diff vs Round 7: nomind +362 keys / vbind +13, zero collisions with pre-existing keys → purely additive.
- AF guard: main-config snapshot (
ev2_af.txt) re-runs byte-identical; the--jointconfig shows a ±0.5pp POS shift that reproduces on a pristine Round-7-source rebuild too — endtable regeneration noise (see lessons), not caused by these additions.ev2_af_full.txtsnapshot refreshed to match. - LXX guard: full driver re-run over all 64 books / 623,685 tokens with the Round-7 convention flags → one token differs (elided μετ' now correctly P/META/ instead of a junky N reading — an improvement from endtable drift).
- pytest 33/33.
First-ever measurement of the Latin analyzer. Evaluator:
tmp/latin/eval_latin.py (MorpheusRunner(language="latin"); PROIEL UPOS mapped to
lenient Morpheus classes; Morpheus's #N homonym markers stripped on both sides).
Gold: PROIEL CoNLL-X (CC BY-NC-SA 4.0 → measurement/docs only, nothing committed).
| corpus | tokens | coverage | top-1 raw | top-1 ranked (AGLDT) | POS raw→ranked |
|---|---|---|---|---|---|
| latin-nt (Vulgate) | 110,774 | 98.4% | 81.1% | 90.5% (+9.4pp) | 78.3→81.9 |
| caes-gal | 27,397 | 96.9% | 74.3% | 85.1% (+10.8pp) | 74.4→78.0 |
| cic-att | 45,580 | 97.1% | 78.0% | 86.5% (+8.5pp) | 72.9→75.1 |
| per-aeth | 17,554 | 94.7% | 74.8% | 83.8% (+9.0pp) | 73.8→78.2 |
| pal-agr | 11,979 | 97.5% | 75.5% | 85.2% (+9.7pp) | 71.7→74.3 |
Table source — license resolution. Index Thomisticus Treebank is CC BY-NC-SA
3.0 (the Digital Classicist wiki footer's "CC BY" is wrong; the project site states
NC-SA), and corpusthomisticum.org offers no clear bulk-download license → rejected.
AGLDT v2.1 (PerseusDL/treebank_data) is CC BY-SA 3.0 US — commit-safe, and its
Latin files carry manual lemma annotations (<word form=… lemma=… postag=…/>,
Perseus-style trailing homonym digits). Built from all 11 Latin treebanks (Caesar,
Cicero, Propertius, Sallust, Vergil Aen. 6, Ovid, Phaedrus, Augustus Res Gestae,
Suetonius, Tacitus, Jerome's Apocalypse): 6,052 lemmas / 55,409 tokens → bundled
as python/morpheus_toolkit/data/lemma_frequencies_latin.json (attribution in the
file; editorial artifacts like -que/heu! filtered).
What holds up. The four classical corpora share no text with the table, yet top-1 rises +8.5 to +10.8pp — ranking generalizes across Latin registers exactly as the Greek tables did (item 3's +12pp transfer). The Vulgate row is only partially circular: the table contains Jerome's complete Apocalypse and PROIEL latin-nt shares 2.85% of its tokens with it in 10-word sequences — negligible, and even so the non-circular AGLDT table (90.5) beats a fully circular PROIEL-derived one measured earlier this round (88.3).
The # homonym-marker fix. Morpheus's Latin stemlib disambiguates irregulars
with #N (sum#1, edo#1, quis#2) and the parser kept them in lemma variants,
so frequency lookups for the most common words in Latin (est→sum) silently missed.
normalize_lemma() now strips trailing #/digit runs (Greek lemmas contain neither
— zero occurrences in all bundled tables; A/B re-run of the AF evaluation with and
without the change is byte-identical). Re-running the circular PROIEL table under
the new normalization: 88.3 → 91.5 — the fix alone is worth ~+3pp on the Vulgate.
Decisions.
- Table carries plain frequencies only, no joint lemma×POS counts: Morpheus's Latin
analyzer cannot separate adjectives from nouns (both surface as
Nunless a special stem type fires), while AGLDT tags them separately — joint counts would systematically demote correct readings. (A future joint table should be derived from Morpheus's own output on a large corpus, self-consistent by construction.) - Not auto-loaded:
Morpheus(language="latin")still ranks with the Koine default unless frequencies are passed — same pattern as the LXX/AF tables. - No stemlib changes from this item; coverage gaps (94.7–98.4%) are a separate, larger effort (the Latin stemlib predates these corpora's rare forms).
Dataset: syntacticus/syntacticus-treebank-data (cloned to tmp/proiel/,
git-ignored) → proiel/greek-nt.conll: Tischendorf 1869, fully manual
annotation, 132,366 word tokens (the CoNLL-X export omits the unannotated
HEB 13 + Catholic epistles; XML has 140,773). License CC BY-NC-SA 4.0 →
measurement/docs only; nothing PROIEL-derived is committed.
Evaluator: tmp/proiel/eval_proiel_nt.py (CoNLL-X reader; gold = col 3 lemma,
col 4 UPOS with a lenient class map; SBLGNT-style #1 homonym suffixes and
οὕτω(ς) optional-parens normalized before comparison).
| metric | value |
|---|---|
| coverage | 99.6% (492 misses) |
| top-1 lemma, raw Morpheus order | 78.0 / 79.3 exact/acc-insens |
| top-1 lemma, ranked with NT tables | 90.7 / 91.7 |
| POS coarse | 81.6% (n=130,209) |
What holds up: the SBLGNT-trained frequency table transfers +12pp to a text it never saw — ranking is robust, not convention-fitted. The gap vs our SBLGNT numbers (~95%) decomposes as:
- Lemma-convention differences (~3,100 tokens ≈ 2.4pp): PROIEL heads the pronoun families at the plural (ὑμεῖς/ἡμεῖς) where SBLGNT/Morpheus use the singular (σύ/ἐγώ); PROIEL lemmatizes εἶπεν to λέγω where Morpheus keeps the reduplicated aorist εἶπον as its own lemma. Convention-adjusted top-1 acc-insens ≈ 94%.
- True residual errors (~7.9pp before adjustment): dominated by known homographs — ὡς→ὅς ×455, εἰ→εἰμί ×329, ἢ→εἰμί ×326, τε→σύ ×213, ὃ/ἃ→ὁ ×~280 — the same Round-6 residual profile (not patched there for overfitting reasons; confirmed here to be stable across independent gold).
- Misses (492): 148 are names, mostly edition spelling/case conventions — PROIEL lowercases σατανᾶς as a common noun while the stemlib entry is uppercase-only (Σατανᾶς resolves fine); PROIEL spells Elijah Ἡλίας (iota) where SBLGNT has Ἠλίας (eta, which resolves). The rest are loanwords/dialectal one-offs (ῥαββεί ×15, ὡσαννὰ, …).
Decision: no stemlib changes from this item — the findings are convention differences between two human-annotated editions of the same text, and adding PROIEL's spellings would change NT behavior (out of scope per user direction) and commit NC-SA-derived content. Numbers documented here only.
Open questions — resolution: pushed morpheus origin/master and the AF repo's
morpheus-analysis branch; user has no interest in further Latin work (the
Round-8 benchmark + bundled table remain as committed; nothing more planned);
PROIEL-derived content stays out of the repo regardless. Record migrated from
todo.md; Round 9 drafted there.
User approved all three items ("Do all 3"); all complete and committed
(6f8ae3a item 1; 2652cbf item 2; 444ea10 item 3). Standing constraints held:
no Latin work, PROIEL-derived content stays out of committed files (NC-SA), default
NT behavior unchanged. Working files in tmp/<corpus>/ (git-ignored): AGLDT clone +
classical evaluator (tmp/agldt/, tmp/grc/), homograph A/B evaluator (tmp/r9/).
- LXX coverage 99.81% (Round 2); NT ~95% top-1; AF 99.35% / 96.6–97.4% lemma (Round 7); PapyGreek 92.9% coverage / 83.7% top-1 with PG tables (Round 8).
- PROIEL cross-validation: the SBLGNT-trained table transfers +12pp to unseen text — ranking is robust, not convention-fitted. The stable true residual across both independent golds is a handful of function-word homographs: ὡς→ὅς ×455, εἰ/ἢ→εἰμί ~×650, τε→σύ ×213, ὃ/ἃ→ὁ ~×280.
- AGLDT v2.1 Greek is much bigger than the Round-8 research table assumed: 557,918 words (Homer ~230K, Thucydides, Herodotus, Sophocles ×3 vols, Aeschylus ×6, Hesiod ×3, Plato, Plutarch ×2, Athenaeus ×2, Diodorus, Polybius, Lysias ×4, Apollodorus, Aesop) — CC BY-SA 3.0 US, commit-safe.
- Classical Greek round ✅ (see review below)
- Baseline: coverage / top-1 / coarse POS over AGLDT v2.1 Greek (557,918 words; per-author breakdown; epic/Ionic dialect is the stress test).
- Build + bundle
lemma_frequencies_grc.json(--format agldt); re-measure ranked top-1. - Stemlib additions: not needed — after the elision fix, residual misses are 708 tokens of editorial fragments and one-off names (see review).
- Guards: AF/PG snapshots byte-identical (or known endtable noise); pytest 33/33.
- Independent cross-check: PROIEL Herodotus (
hdt.conll, CC BY-NC-SA → measurement/docs only, nothing committed).
- Contextual homograph disambiguation ✅ (see review below)
- Profile the residual homographs on SBLGNT/AF/PG gold to fix the form set and context features.
- Train a small learned model P(lemma | form, prev POS, next POS) on
commit-safe gold only (SBLGNT + AF + PG; LXX excluded — no annotated
.mlxxgold in this workspace); bundle as data table. - Wire in behind an opt-in flag (API + CLI); default path byte-identical.
- Verify: measured +0.6–1.6pp top-1 on SBLGNT/AF/PapyGreek (below the +2–4pp target — see review for why that is the ceiling); transfers to PROIEL greek-nt (independent gold); pytest 33/33.
- PapyGreek numerals ✅ (see review below)
- Inspect ALDT numeral gold (postag
m): forms, lemmas, marker patterns. - Build bundled papyrus-numeral table from PapyGreek gold (CC BY-SA);
opt-in flag emits synthetic analyses for unanalyzed numeral tokens
(
unknown_as_properprecedent) — never silent stemlib additions. - Verify: coverage +2.2pp on PapyGreek; AF/SBLGNT default paths byte-identical, PG baseline matches Round 8's snapshot; pytest 33/33.
- Inspect ALDT numeral gold (postag
Dataset: AGLDT v2.1 Greek (tmp/agldt/, CC BY-SA 3.0 US): 469,254 word tokens
across Homer (~200K), Sophocles, Aeschylus, Athenaeus, Herodotus, Polybius,
Diodorus, Thucydides, Plutarch, Hesiod, Lysias, Plato, Aesop, Homeric Hymns,
Apollodorus. Evaluator: tmp/grc/eval_grc.py (per-author breakdown; AGLDT postag
pos-1 empirically mapped — note it differs from Latin: g=particle, p=pronoun,
r=preposition, l=article, m=numeral).
The one systematic gap was elision-mark encoding. Baseline coverage was 92.8%
(Homer 90.3%, Sophocles 88.5%) with the top misses all word-final U+0313 (combining
comma above, the classical elision mark): δ̓ ×10,778 alone = 2.3% of the corpus.
to_beta() now maps a word-final U+0313 to the ASCII apostrophe Morpheus already
recognizes (mid-word combining marks untouched). Every top-30 elided form was
verified to resolve through existing stemlib entries — no new stems needed for them.
Safety: SBLGNT/AF/PapyGreek gold contain zero word-final U+0313/U+0306 (scanned);
LXX is beta code, so structurally impossible. Result:
| metric | before fix | after fix |
|---|---|---|
| coverage (ALL) | 92.8% | 99.8% |
| Homer | 90.3% | 100.0% |
| Sophocles / Aeschylus / Hesiod | 88.5 / 90.2 / 90.6% | 99.8 / 99.6 / 99.7% |
Bundled table: data/lemma_frequencies_grc.json (21,889 lemmas / 476,401
tokens; attribution in file). Ranked results per author:
| author | n | coverage | top-1 raw → ranked | POS |
|---|---|---|---|---|
| ALL | 469,254 | 99.8% | 79.9 → 91.4% (+11.5pp) | 85.1% |
| Homer | 200,091 | 100.0% | 78.5 → 91.2% | 88.1% |
| Sophocles | 40,851 | 99.8% | 75.8 → 88.9% | 83.7% |
| Aeschylus | 39,479 | 99.6% | 78.1 → 89.4% | 87.3% |
| Athenaeus | 33,272 | 99.2% | 82.0 → 91.8% | 81.1% |
| Herodotus | 28,791 | 100.0% | 81.8 → 91.8% | 79.1% |
| Polybius | 25,528 | 100.0% | 84.1 → 93.9% | 83.4% |
| Diodorus Siculus | 23,037 | 99.9% | 86.2 → 95.4% | 84.6% |
| Thucydides | 22,336 | 99.9% | 83.5 → 92.5% | 78.8% |
| Plutarch | 19,386 | 99.9% | 85.3 → 93.9% | 81.6% |
| Hesiod | 16,280 | 99.7% | 77.7 → 92.3% | 90.6% |
No stemlib additions needed. Residual misses: 708 tokens / 527 forms (0.15%) — editorial fragments (τ- ×47, κ- ×40), one-off rare names (×1–6 each from Athenaeus/Plutarch/Diodorus), and <40 epic-dialectal forms (γώ/γὼ→ἐγώ, σθω/σθαι, πὶ/φὶ). Adding ~500 one-off names to the shared stemlib would be bloat for 0.1% — deliberately not done (contrast PapyGreek, where misses were systematic).
Independent cross-check — PROIEL Herodotus (hdt.conll, 83,819 tokens,
CC BY-NC-SA → docs only): coverage 99.8%; top-1 raw 79.9/81.5 exact/acc-insens;
ranked with NT tables 86.3/87.6, with the classical grc table 90.2/91.7 — the
classical table beats the Koine one by +4pp on a text it shares no words with,
and Morpheus's coverage of Ionic prose matches AGLDT's (100%).
Verification: pytest 33/33; AF main-config re-run adds zero drift vs Round 8's check run (both show only the known endtable noise); PapyGreek eval byte-identical in both table configs.
Design. Opt-in re-ranker (Morpheus(use_homograph_context=True) / CLI
--homographs, Greek only; off by default — the table is not even loaded when off).
Context = the neighbours' frequency-ranked top-1 POS (Morpheus's own output, not
gold POS — no train/test mismatch); verse/sentence boundaries reset context.
Scoring is log1p(count) with exact-context → marginal fallback; tokens whose form
is not in the table are untouched. New module morpheus_toolkit/homographs.py
tables by beta form and lazy-loads only when the flag is on.
Training. Commit-safe gold only: SBLGNT + Apostolic Fathers + PapyGreek (all CC
BY-SA, 237,231 tokens). LXX deliberately excluded — no annotated .mlxx gold exists
in this workspace (only Morpheus's own outputs in tmp/lxx/); documented as a
limitation. Table: 644 forms (min_form=20, min_ctx=5) →
data/homograph_contexts.json; rebuild with scripts/build_homograph_contexts.py
(idempotent; readers for all three gold formats).
Result — A/B (flag off → on, gold = each corpus's own lemma):
| corpus | n | top-1 | acc-insens | POS |
|---|---|---|---|---|
| SBLGNT (in-domain) | 137,554 | 93.6 → 94.4 (+0.8pp) | 94.2 → 95.0 | 77.6 → 78.2 |
| Apostolic Fathers | 63,222 | 89.0 → 89.7 (+0.7pp) | 89.6 → 90.3 | 75.0 → 75.5 |
| PapyGreek | 36,455 | 83.8 → 85.4 (+1.6pp) | 85.3 → 86.9 | 77.3 → 78.5 |
| PROIEL greek-nt (independent) | 132,366 | 92.4 → 93.0 (+0.6pp) | 93.4 → 94.0 | 82.7 → 83.1 |
Positive on all four corpora including the fully independent PROIEL gold —
transfer confirmed, not memorization. But +0.6–1.6pp, not the +2–4pp target: only
42 of the 644 table forms have non-degenerate marginals (and just 13 any context
split) — Koine gold is ~95–100% one-way for most ambiguous forms, and several
"splits" are accent/case annotation conventions (te→τέ/τε, ti/→τίς/τις). The famous
residual families from the Round-8 cross-validation (w(s→ὡς unanimous in training,
ei)→εἰ unanimous, o(\→ὅς unanimous) are inter-edition convention differences with
PROIEL, not ranking errors — the model correctly doesn't chase them. Shipped as
opt-in tooling; the larger residual is convention noise no ranker can fix (lesson
captured in lessons.md).
Verification: pytest 33/33; AF default-path re-run byte-identical to Round 9's
check snapshot (tmp/af/ev2_af_r9check.txt) — flag off is a proven no-op.
Gold profile. The ALDT treebank has 1,454 numeral tokens (postag m); 853
are currently missed — all bare Greek-letter numerals (β = 2, κθ = 90, λϛ = 36 …;
the corpus stores them without a visible numeral sign), only 83 distinct forms. Of the
601 "analyzed" numerals: word-numerals Morpheus handles fine (χίλιοι, δέκα — tagged N in
85% of cases) plus false hits: short betas collide with real stems under
accent-insensitive matching (κε→ἄν, ιε→εἶμι, ιη→κερατέα — 315 of 348 letter-numeral hits
carry the wrong top-1). Those are deliberately NOT touched: overriding analyzed tokens
would corrupt real one/two-letter particles (κε = καί) in other corpora.
Feature. data/papyrus_numerals.json (50 forms; PapyGreek numeral gold, CC BY-SA):
for each form Morpheus misses, the majority-vote gold lemma keyed by beta code. Rebuild:
scripts/build_papyrus_numerals.py (pipe annotations stripped from lemmas too; only pure
letter lemmas count as reliable — a handful of decimal-annotated tokens like Ατ→1300
are excluded). Opt-in via Morpheus(numerals=True) / CLI --numerals (new module
morpheus_toolkit/numerals.py, Greek only): unanalyzed tokens whose beta is in the table
get a synthetic analysis (POS N, proposed=True). Runs before unknown_as_proper
(more specific wins); off by default — no table loaded, behavior unchanged.
Result (PG evaluator + PG frequency table; flag off → on):
| metric | off | on |
|---|---|---|
| coverage | 92.9% | 95.1% (+2.2pp) |
| numerals missed | 853/1454 | 38/1454 |
| top-1 ranked (exact / acc-insens) | 83.7 / 85.1% | 85.9 / 87.2% (+2.2pp) |
All 815 missed tokens whose beta is in the table are recovered; the residual 38 are forms
below min_count=2 (one-off editorial variants). Recovered numerals are correct by
construction (lemma = gold letter string), so top-1 rises too — except a handful of
decimal-annotated tokens that now count as covered-but-mismatching, and 4 in-domain false
positives (τ = ἀντί abbreviation ×3 + one editorial placeholder) get labeled numerals;
consumers can filter on proposed=True. The POS metric drops 81.7→79.8% purely as a
denominator artifact: that evaluator maps treebank m → class X and Morpheus has no numeral
tag, so every papyrus numeral counts as a POS mismatch both before and after.
Verification: pytest 33/33; PG baseline byte-identical to Round 8's saved snapshot;
AF re-run byte-identical (tmp/af/ev2_af_r9check.txt); SBLGNT flag-off A/B line identical —
default path unchanged on all three corpora.
- Any Latin work (user direction for Round 9); the Round-8 Latin benchmark + table remain as committed, untouched.
- PROIEL-derived content in committed files (CC BY-NC-SA; measurement/docs only).
- NT behavior changes (Round-7 direction): no stemlib edits that alter SBLGNT/NT output to fit another edition's conventions.
Open questions — resolution: user chose to stop here ("Migrate, commit, push and
stop"); record migrated from todo.md; morpheus origin/master pushed. No further rounds
planned at this time. Candidate follow-ups if resumed: retrain the homograph table once
annotated LXX gold is available in the workspace (excluded — no .mlxx gold locally);
the 38 sub-threshold numeral forms are diminishing returns; fixing the false-hit letter
numerals (315 wrong top-1 on PapyGreek) would need stemlib entries that change default
behavior, out of scope per user direction.
User approved the whole plan ("Process"/"Do 2 and 3"/"Do 10-13"/"Do both"); complete
2026-10-14 → 2026-10-15. Working files in tmp/r10/ (git-ignored). Standing constraints
held throughout: no Latin, PROIEL content out of committed files, NT behavior unchanged,
no GitHub processing. Migrated from todo.md (2026-10-15). improvements.md gained a
"Round 10 additions" section at the end.
- Item 1 — learned disambiguation model: done (opt-in). New
python/morpheus_toolkit/learned.py(shared feature extractor +LearnedRanker),scripts/build_learned_model.py(per-token averaged perceptron), bundleddata/learned_reranker.json(488 features; trained on SBLGNT + AF, 188,656 analyzable tokens). Features: lemma frequency + its rank, fine POS, stem type, morphological feature values, POS interactions with the neighbours' frequency-ranked POS. APIMorpheus(use_learned=True)/ CLI--learned. Top-1 lemma, frequency → learned: SBLGNT 94.6→95.0, AF 90.1→90.6 (MorphGNT subset 95.9→96.4), independent PROIEL 90.7→91.5 (+0.8), AGLDT classical 91.4→92.1 (every author positive). Uniformly positive → generalization; kept opt-in (not yet validated on LXX/PapyGreek). Adding morphological-feature tokens was the change that made it uniformly positive; a POS-only first version dipped on Homer/Diodorus. Evaluators gained a--learnedflag. - Item 2 — adaptive domain-table selection: done (opt-in). New
python/morpheus_toolkit/tables.py(TableSet): scores every bundled table against the input's candidate lemmas by normalized rank inside each table (size-robust), picks the best fit;blend()provided.Morpheus(adaptive_tables=True)/--adaptive-tables. Selection correct on every register (SBLGNT→Koine, AF→AF, AGLDT→classical, PROIEL NT→ Koine) and matches the best single table: AGLDT classical 85.3→91.4 (+6.1) vs the Koine default, AF 90.1→90.5, SBLGNT unchanged. Kept opt-in. 4 unit tests. - Item 3 — lemma-context features: tried, reverted. Added neighbour closed-class
lemma features (gated
--lemma-context, off) and retrained: worse (PROIEL 91.5→91.4, AGLDT 92.1→91.9, Diodorus 95.9→95.2) despite lower train error — the predicted-neighbour lemma is noisy and the features overfit. 488-feature model restored. - Item 4 —
-έλλωaorist passive: fixed. Root cause was not the compound scanner:ἐγγελάωis a real rare lemma, and the true gap was that the aorist passive of -έλλω verbs was never generated (simplex ἠγγέλη fell back to ἐγγελάω). Addedstemlib/Greek/stemsrc/vbs.r10(explicit:vb:lines, stemtypeaor_pass) wired into the vbind cat list. Verified ἠγγέλη → ἀγγέλλω, ἀπηγγέλη → ἀπαγγέλλω (wrong ἐγγελάω gone), καταγγέλη → καταγγέλλω. - Item 5 —
-πλpers_name artifact: fixed.addconstraints.plemitted a:le:<lemma>-plis_groupentry perpers_name; because the engine generates every number from one stem+ending class, the group entry duplicated the surface forms and made singular forms return the bogus-πλalias. Fixed by emitting the group entries under the base lemma (:le:$curlemma), so indexnoms merges them. Verified:-πλtop-1 lemmas SBLGNT 4/40K → 0, AGLDT 26/120K → 0; affected names now match gold where the base lemma is gold-correct (Συβαριτῶν→Συβαρίτης, Εὐμολπιδῶν→Εὐμολπίδαι, Ἀθηνᾶ→Ἀθήνη, βορρᾶ→Βορέας); plural/oblique still resolve; nomind shrank (4,367,409 → 4,336,597 B). - Item 6 —
-μπfuture-middle: already covered. The Round-2 explicit:vb:lines resolve every standard λαμβάνω future (λήμψομαι/ῃ/εται/όμεθα/εσθε/ονται); non-standard λήψεται correctly returns nothing. - Item 7 — interjection POS: fixed. Interjections carry stem type
exclam, whichfine_pos()did not map; mappedexclam→I(οἴμοι/φεῦ/ἰού). Unit test added. - Item 8 — offline benchmark harness: done.
scripts/benchmark.pyreads whichever corpora are present (env:MORPHGNT_DIR/AF_CORPUS_DIR/AGLDT_DIR/PROIEL_DIR), prints analyzed / coverage / top-1 (raw→ranked→learned) / POS, checks committed thresholdsbenchmarks/expected_greek.json(tolerance-gated, non-zero exit;--updateregenerates), and prints paired bootstrap CIs (--bootstrap). Offline only. - Item 9 — deterministic builds: done. Two
make cleanrebuilds are byte-identical (equal to HEAD) forsteminds/,endtables/,derivs/; the only deltas are the compiled STEMPROG helper binaries (build metadata, no analysis effect). The historical ±0.5pp drift is cross-environment. Hardened the makefile with$(sort $(wildcard …)); verified neutral. - Item 10 — confidence calibration: done.
calibration.py+scripts/calibrate.py(temperature by NLL grid search;data/calibration.json, T≈0.08). Applied by default; top-1 ECE 0.207→0.141 (SBLGNT), 0.203→0.139 (AF);temperature=1.0opts out. - Item 11 — convention profiles: done (mechanism).
profiles.pyregistry +Morpheus(profile=…)/--profile. Finding: Morpheus's Unicode output already matches the MorphGNT/LXX dictionary-form conventions (articles ὁ/RA, pronouns ἐγώ/σύ, demonstratives οὗτος/ἐκεῖνος), so the bundled transform is idempotent. Follow-up 11.2 (--base-lemmasprofile) deferred to Round 11. - Item 12 — Unicode-native path: scoped.
docs/unicode-native-input.md(NFD + mapping tables insrc/greeklib/gkunicode.c, explicit-uflag,to_betaoracle test; ~2.5 days). Implementation deferred to Round 11. - Item 13 — scale/packaging: done.
Morpheus(jobs=N)/--jobs→ 3.06× at jobs=4 on the Apostolic Fathers (60.2s→19.7s), identical output.scripts/package.shbuilds a wheel locally (dist/, git-ignored).
pytest→ 49 passed (was 38; +learned/tables/calibration/profiles tests).- Offline benchmark gate: full run reproduces the evaluators and a re-run prints "OK (within tolerance)".
- Ruby C suite: 11 failures on macOS — reproduced on the committed stemlib too → pre-existing platform candidate-ordering differences, not regressions.
- Flag-off paths unchanged (learned/adaptive load nothing when off); calibration changes only reported confidence, not rankings.
- Latin work; PROIEL content in committed files (CC BY-NC-SA); NT behavior changes; any GitHub processing.
- Coverage fishing already declined: classical one-off names/fragments (0.15%), the LXX ἕως/ἠώς homograph, the 38 sub-threshold PapyGreek numerals, bare-letter numeral false hits.
Executed 2026-10-15. Working files in tmp/r10/ and tmp/agldt/. Only Item A
(AGLDT harvesting) and the round wrap-up (Item C) were executed; Item B remained a
candidate list and is carried forward in todo.md. Migrated from todo.md per the new
process rule; improvements.md re-evaluated (Round 11 section).
- A1 —
--format agldtreader: done. Addedread_agldt()+assign_chunks()toscripts/build_learned_model.py(regex over<word form=… lemma=… postag=…>, one context chunk per file, skip postagu/m, map pos1 → coarse POS). Verified 468,596 AGLDT rows. Analyzer calls are batched (4000) because AGLDT files are huge single chunks. - A2 — pool AGLDT into the learned re-ranker: done (balanced). Two runs:
- pooled-all (SBLGNT+AF+AGLDT = 650,360 tokens): classical +3.25 but Koine regressed (SBLGNT 95.0→94.3, AF 90.6→90.0, PROIEL 92.1→91.7) — 70% classical pool.
- balanced (
--agldt-limit 200000; 385,758 tokens) shipped as the bundled model: AGLDT 88.5→91.5 (Koine table) / 92.1→93.2 (classical table); independent PROIEL +0.13 (92.1→92.2); Koine −0.3 (SBLGNT 94.7, AF 90.3) — within the ±0.5pp build-noise band. Added the--agldt-limituniform-subsample control.
- A3 — classical POS model: built; negative result.
scripts/build_pos_model.py --format agldt→data/pos_bigram_grc.json(10 tags). Measured via the evaluator's new--tagger/--model: the classical bigrams beat the Koine ones for classical when the tagger is used (Homer POS 87.3 vs 84.8), but the tagger still trails plain frequency-ranked POS (ALL 85.2 vs 83.6) — consistent with the long-standing finding that the bigram Viterbi tagger does not pay off. - A4 — provenance: done. AGLDT v2.1 is CC BY-SA 3.0 US (commit-safe); PROIEL stays the
independent, measurement-only gold. Records the inventory finding:
AGDT2(Aesop + P.-Apollodorus) is already in v2.1;v2.0/v1.6/v1are older copies;IGDSis ~73 words. - A5 — re-baseline: done.
benchmarks/expected_greek.jsonregenerated with the pooled model; harness gained per-group batching (large AGLDT files were timing out as single cruncher calls).
Not executed (kept as candidates in todo.md): default-on validation (B1), a proper
POS/sequence model (B2), the Unicode-native C path (B3), per-config/POS calibration (B4),
the --base-lemmas profile (B5), cross-environment build determinism (B6), tokenizer
robustness (B7).
- C1 —
improvements.md: updated (Round 11 additions section; header date). - C2 — migration: this record.
todo.mdnow holds Round 12 (proposed) and the carried-forward candidates.
pytest49 green;build_learned_model.py/build_pos_model.py/eval_grc.pycompile.- Benchmark: pooled model run + thresholds rewritten; gate passes.
- Full A/B across SBLGNT / AF / AGLDT / PROIEL recorded above.
Executed 2026-10-15. Working files in tmp/grammateus/ — all corpus-derived data,
gold, predictions and model artifacts stay there; nothing DDbDP/OIKONOMIA-derived is
committed (this record holds measurements only, per the standing constraint). Migrated
from todo.md per the process rule; improvements.md re-evaluated (Round 14 section).
The toolkit is a lemma/POS/numeral analyzer with a bundled learned re-ranker (top-1 lemma: SBLGNT 94.7, AF 90.3, PROIEL 92.2, AGLDT 93.2) and an opt-in papyrus-numeral table. It has no entity layer. Round 14 asked two questions: what does the analyzer contribute under a real entity-extraction pipeline on documentary papyri, and can that pipeline be reproduced locally from public artifacts.
14.1 joint pipeline prototype · 14.2 three extractor options with true P/R · 14.3 Morpheus-driven recall boost · 14.4 canonicalized entity output · 14.5 route comparison · 14.6 fine-tune on the public gold. Guardrail: nothing OIKONOMIA/DDbDP-derived committed.
tmp/grammateus/pipeline.py — NER extract → Morpheus canonicalize/soft-validate → optional
LLM second pass. Demonstrated on a real documentary segment (PHI7 DDP0006): the NER found
14 entities (64% Morpheus-recognized), qwen 15 (67%), agreement only 6/22; Morpheus
canonicalized real entities (χαλκοῦ→χαλκός, δραχμῶν→δραχμή, ἥμισυ→ἥμισυς) and flagged
suspect spans. Two refinements identified up front: separate "not a word" from "name
Morpheus lacks", and validate multi-word spans.
- Options. (1) OIKONOMIA-DB (CC-BY-3.0 parquet, 61,249 docs) is fetchable and a big
reference/index, but it is the same model's output (circular for NER) and
DDbDP-derived. (2) Fine-tuned NER checkpoint (Apache-2.0) runs locally, deterministic
and fast — the extractor to pair with Morpheus. (3) Hand gold was superseded: the
real 115-document gold is public (
data/gold/annotated.jsonl: 115 docs / 2,995 entities / 15 labels, model-drafted + model-checked, not expert-validated). - Harness.
tmp/grammateus/eval_gold.pymirrors the project's own inference (src/oikonomia/ner/{inference,encode}.py: tokenize → argmax →decode_spans, strided token windows). Text is NFC (NFD input destroys the model: F1 0.002); lowercase hurts. - True P/R on the gold (zero-shot). Shipped NER checkpoint: strict P 0.593 / R 0.567 / F1 0.580; relaxed 0.683; lemma-space 0.594. qwen base prompt: strict F1 0.229 (P 0.394 / R 0.162); relaxed 0.418; lemma-space 0.274. Exhaustive prompt (19 docs): strict 0.319 / relaxed 0.566 vs NER 0.616 / 0.729 on the same subset.
- Checkpoint diagnosis. The card's stage-2 model was trained on all 115 docs (AGE F1 0.974). The shipped artifact scores the same on the 85 pre-expansion docs (0.587) and the 30 new docs (0.580), has no memorization, and predicts 0 AGE — behaviour matches the card's silver-stage model (card 0.654; measured 0.580 zero-shot), not stage 2. Weak labels: OCCUPATION (20% covered), COMMODITY 45%, TAX_TERM 41%, TRANSACTION 56%; confusions PLACE→PERSON (69), numeral family (~130). Item 6 confirms and fixes this.
- DB ≠ gold on the same 115 docs (PERSON 958 vs 840, MONEY_AMOUNT 185 vs 223,
has_price0 vs 10,has_tax1 vs 29): the DB is a linkable reference, not a gold substitute.
- Candidates (deterministic). After NER, Morpheus flags unlabelled content tokens
(POS N/A or
stemtype=numeral, notproposed): 2,476 candidates over the 115 docs; 425 of the 931 missed gold spans (46%) contain one — OCCUPATION 75%, COMMODITY 81%, TAX_TERM 78%, PERSON_ROLE 94%, QUANTITY 65%, MONEY_AMOUNT 36%; AGE only 1% (its spans sit inside wrong-label DATE_REF predictions — a relabelling problem). WithMorpheus(numerals=True)the set grows 2,476→2,526 and reachability 425→430 (+5 spans; AGE 1%→2%) — letter-numerals are not the AGE bottleneck. - Full 115-doc LLM pass (one call per doc over just the candidates,
boost_llm.json): naive merge is F1-negative (strict 0.580→0.519, relaxed 0.683→0.620 — strict R rises 0.567→0.632 but the LLM over-labels). Label-filtered merge is net-positive: whitelist {COMMODITY, OCCUPATION, TRANSACTION, MONEY_AMOUNT} → strict P 0.559 / R 0.613 / F1 0.585, relaxed 0.689 (+4.6pp strict recall). - Proposal quality (1,435 proposals; 14% strict-precise, 22% relaxed-precise; relaxed precision): OCCUPATION 0.72 (70 of 154 missed recovered), COMMODITY 0.43 (73/113), MONEY_AMOUNT 0.36 (12/106), TRANSACTION 0.10 (10/29); whitelist = 422 proposals, 41% relaxed-precise, 165 missed spans recovered. Outside the whitelist relaxed precision is ≤0.22 (PERSON 0.47 but only 0.03 strict — boundary noise; QUANTITY 0.20; PLACE 0.22; UNIT/CURRENCY/PRICE_TERM/AGE ≈0).
- Verdict: the second pass works only as a whitelist-filtered, recall-oriented layer; Morpheus candidate generation is what makes it cheap (one call per document).
- It does not survive a stronger extractor. The same recipe re-run on the
fine-tuned model's held-out output (
boost_llm_ft.json): proposals drop to 5% strict / 8% relaxed precise (whitelist 16% — 33 missed spans recovered) and the merge turns F1-negative: strict 0.717→0.700 (P 0.648 / R 0.760), relaxed 0.814→0.795. A good extractor's remaining misses are the hard ones; the boost is a crutch for a weak extractor, not a permanent layer.
tmp/grammateus/canonical.py re-keys every entity by Morpheus: per-word lemma/POS/
features/confidence plus a canonical key (lemma sequence + label); artifacts
canonical_entities_ner.jsonl, canonical_entities_ner_boost.jsonl. Label-aware policy:
Morpheus(numerals=True, unknown_as_proper=True); PERSON/PLACE prefer a proposed (name)
analysis, and a capitalized token whose best stemlib analysis is a verb (genitive-name
false friends) keeps its surface as the safe canonical form. Measured: shipped NER 2,867
entities, 53% fully known, 5% of surface keys collapse in canonical space, lemma-space F1
0.580→0.594; fine-tuned model 3,272 entities, 53% known, 4% collapse, lemma-space F1
0.717→0.725 — the morphology layer's gain shrinks as the extractor improves. Two
toolkit gaps recorded in improvements.md: letter-numerals are opt-in and the lowercase
letter for 1 is missing from the table; verb false friends outrank name proposals for
analysed tokens.
Micro F1 on the 115-doc gold (strict / relaxed / lemma-space):
| route | strict F1 | relaxed F1 | lemma F1 |
|---|---|---|---|
| qwen base prompt | 0.229 | 0.418 | 0.274 |
| shipped NER checkpoint (zero-shot) | 0.580 | 0.683 | 0.594 |
| shipped NER + whitelist boost | 0.585 | 0.689 | — |
| fine-tuned from GreBerta (gold only, held-out) | 0.295 | 0.516 | 0.295 |
| fine-tuned from shipped ckpt (held-out) | 0.717 | 0.814 | 0.725 |
| fine-tuned + whitelist boost (held-out) | 0.700 | 0.795 | — |
| fine-tuned final (train-on-test) | 0.981 | 0.990 | 0.979 |
(The boost row is net-negative on the fine-tuned base — see item 3.)
Morpheus-layer measurements (held-out fine-tuned preds): 54% of predictions fully Morpheus-validated / 11% partial / 35% none; strict-match rate 0.705 (validated) vs 0.664 (not) — a soft signal (hard-filtering drops strict F1 0.717→0.522); 61% of remaining misses contain Morpheus-known words.
tmp/grammateus/finetune.py, the project's stage-2 recipe (lr 3e-5, linear + warmup,
15 epochs, batch 8, max_len 512, seed 17; 5-fold CV + final). Two runs:
- From base GreBerta (gold only): weak. CV strict F1 0.296 / relaxed 0.518 (AGE 0.462, CURRENCY 0.507, UNIT 0.481; COMMODITY 0.041, OCCUPATION 0.076) — gold-only fine-tuning on a non-domain backbone doesn't transfer; consistent with the card's no-DAPT control (0.495 on the 65-doc gold).
- From the shipped checkpoint: CV strict F1 0.717 / relaxed 0.816 — the card's 0.737/0.837 reproduced within fold noise, at laptop scale (~25 min on MPS). Per-label held-out (card value): AGE 0.932 (0.974), PERSON 0.767 (0.775), CURRENCY 0.835 (0.822), UNIT 0.845 (0.841), PLACE 0.648 (0.650), MONEY_AMOUNT 0.744 (0.758), QUANTITY 0.690 (0.744), DATE_REF 0.644 (0.690), OCCUPATION 0.696 (0.746), TRANSACTION 0.597 (0.602), PRICE_TERM 0.929 (0.929), FRACTION 0.785 (0.863). Final model (all 115, train-on-test) 0.981 strict / 0.990 relaxed — training pipeline verified.
Outcome: the shipped artifact is the silver-stage model; the card's stage-2 quality is
reproducible locally by fine-tuning that checkpoint on the public gold. Model artifacts
stay in tmp/grammateus/model_gold{,2}/.
pytest python/tests— 49 passed (no toolkit code changed; the bare-pytestrootdir collision with the scratchtmp/r10/test_tables.pyis pre-existing and ignored).- All Round 14 numbers re-derivable offline from
tmp/grammateus/(eval_gold.py,eval_folds.py,compare_all.py,morpheus_layer.py,canonical.py,recall_boost*.py,merge_boost.py,finetune.py); LLM calls only via local LM Studio. - Benchmark gate untouched (no shipped-code changes).
- Nothing TLG/PHI-derived and nothing DDbDP/OIKONOMIA-derived is committed (personal-study use only); PROIEL-derived content stays measurement-only.
- No GitHub CI runs; pushes use
[skip ci].
Closed 2026-10-15 as a documented feasibility study (Option A). Executed as a
local-only probe; closed without toolkit changes because the license-clean actionable
upside is small and the big miss buckets are either inherent or not committable. Tooling
stays in git-ignored tmp/phi/ (probe.py, scan.py, lsj_filter.py); this record is
method/counts only — no PHI/TLG-derived data in any committed file. improvements.md
re-evaluated: no change (no shipped-code effect).
- Format decoded; extraction solved without porting the Delphi parser. PHI7
.TXTkeeps all reference/metadata (region, author, work, citation, nibble codes) in high-ASCII bytes (≥0x80) and the TLG beta text in 7-bit ASCII. Dropping ≥0x80 and stripping TLG/Leiden markup (numeric codes, bracket/brace wrappers, lacunae, underdots, line-break hyphens) yields clean beta. Validated against the user's ownBetaCode/Attica.TXTexport: 99.6% distinct / 99.9% token overlap. - Regions: inscriptions ~49 MB, documentary papyri ~45 MB, Christian ~12 MB (Coptic excluded).
- Coverage probe: documentary Greek analyzes at 73.5% — the weakest register (cf. PapyGreek 92.9% after Round 8).
- Miss taxonomy (26.5% missed token mass): ~50% ≤4-letter fragments (inherent to broken texts); ~21.5% (466,789 tokens) proper names, mostly not in LSJ (so not committable); a documentary word family (~15K tokens); a few common verb forms; ~17K numeral tokens (tally-style); a small line-split tokenizer leak in the probe extractor.
- License-clean filter: matching the 383,888 missed distinct forms against public-domain LSJ (accent-skeleton + stem + plausible-ending rule; prefix entries excluded) leaves 14,097 forms / 61,318 tokens = 2.8% of misses (≈0.75% of the corpus) mapping to 6,127 candidate lemmas — still fragment-contaminated; the genuinely real subset (e.g. LSJ-attested ὑοθεσία, Ἡρακλῆς, Σάραπις, Θερμοῦθις, δῆμος, στῆλη, εὐλογία, ἀρτάβη) is smaller still.
- The two big buckets are inherent (fragments) or not committable (names).
- The committable remainder is below 0.75% of tokens after curation, against the cost of curation + a new generated source + stemlib rebuild + benchmark re-baseline.
- The documentary register is already served by committable PapyGreek-derived work
(Rounds 8/9:
nom.papyri, numeral table). - Closed as a feasibility study; 12.3–12.6 not executed. The probe vindicates the earlier "coverage probe only / probably not a windfall" stance with data.
- The I.PHI/Pythia idea (candidate B9) is moot for Round 12; lacuna restoration as a pre-pass for fragmentary texts remains an open candidate note.
- A numeral-table extension for tally-style numerals is not worth pursuing for PHI under the license rule; Round 9's table already covers letter-numerals.
- Extraction validated against the user's own export (99.6% distinct / 99.9% token).
- No shipped-code changes;
pytestgreen; benchmark gate untouched.
Ship as much of the B-item backlog as can move the ## Accuracy numbers before the
CSNTM/Wallace contact: the B1 default flip first, then whatever else measures today.
-
API:
Morpheus(use_learned=True, adaptive_tables=True)by default; opt out withuse_learned=False/adaptive_tables=False. Caller-suppliedfrequencies=are never overridden (new_explicit_frequenciesguard). CLI:--no-learned/--no-adaptive-tables; the old positive flags remain as hidden no-op aliases. -
Benchmark: defaults flipped to match (
--no-learned/--no-adaptiveopt-outs); the canonical gate command is now plainscripts/benchmark.py;--no-gateadded for opt-out diagnostics. -
Validation (identical candidate sets; corpus-level top-1 lemma):
corpus old default new default gain SBLGNT (137,554) 94.56 94.67 +0.1 Apostolic Fathers (63,227) 90.14 90.21 +0.1 AGLDT classical (469,254) 86.56 93.74 +7.2 PROIEL Greek NT (132,366) 91.66 92.19 +0.5 LXX (623,685, exact) 88.4 90.7 +2.3 PapyGreek (36,455, exact) 81.2 85.7 +4.5 -
Adaptive selection picked the right register on every corpus (fit margins): SBLGNT→Koine (0.91), AF→AF (0.89), AGLDT→classical (0.95), PROIEL→Koine (0.91), LXX→LXX (0.93 vs 0.88), PapyGreek→papyri (0.72 vs 0.68).
-
Re-baselined
benchmarks/expected_greek.jsonfrom the shipped-default run. -
Interaction recorded: on AF, learned + AF table (90.21) vs frequency + AF table (90.45) — the learned model's features were trained against the default table, so a selected table helps its fallback order more than its scores. Net vs old default still positive (+0.07).
-
Tooling fixes found along the way:
select_adaptive_tableanalyzed group-by-group (a cruncher spawn per verse/sentence — minutes wasted); now flattens the sample and batches it. Non-learned runs no longer report a phantomlearned=0.0%(miss-branch flag leak inCounters). -
Tests: 3 new API tests (defaults on, opt-outs, explicit-frequencies guard) — 56 pass.
- The bundled temperature (T=0.08) was fitted on frequency scores; after the flip the default path reports learned scores on a different scale.
calibrate.py --learnedfits the learned path and mergestemperature_learned+ ECE fields intocalibration.json(merge keeps both fits in one file);load_temperature(learned=...)andapi._annotateselect by the score path that actually produced the order (_learned_scorestracks it; the context/homograph passes keep the historical frequency temperature).- Measured: the learned path is natively calibrated → T=1.0, ECE 0.0054 (SBLGNT) / 0.0024 (AF), vs frequency T=0.08 ECE 0.1416 / 0.1393 (raw 0.208 / 0.2034). A probe confirmed the learned path is insensitive to T ∈ [0.08, 1.0] (ECE ~0.005), so the pre-flip mismatch was numerically harmless — but the selection is now correct and the default-path confidence is ~30× tighter than the old default's.
- Remaining: joint-path temperature + calibrated POS confidence (B4 stays open).
- Lowercase α/ε were absent from the table while their capitals were present.
propose()now falls back to the capital entry (*prefix) for single letters — numeral case is meaningless. PapyGreek--numerals: numeral misses 853 → 38 of 1,454; coverage 92.9% → 95.1%; top-1 87.8% exact with papyri + learned. - New
test_numerals.py(2 tests).
- Harness
tmp/lm/head2head.py(proto.py extended): identical example sets (≥2 candidates, gold among them), four scorers, corpus-level projection. The AF and PROIEL projections reproduce the benchmark exactly (90.1/90.3, 91.7/92.2) — the harness is comparable toscripts/benchmark.py. - Result: the LM head wins on accented text (SBLGNT test 99.4 vs learned 97.9; AF 98.0
vs 97.3 ambiguous-only; corpus-level +0.5 / +0.2) but regresses on the shipped
accent-insensitive pipeline (PROIEL 92.3 vs learned 96.4 ambiguous-only; 89.9 vs
92.2 corpus-level). Mechanism measured:
ignore_accentsinflates PROIEL's multi-candidate rate 37.8% → 59.4% (38.8% vs 22.1% distinct-lemma) — accent-variant homographs the LM head (trained on accented SBLGNT only) never saw. - Combined ≈ LM (the learned-rank feature adds nothing).
- Not shipped (
torch+ ~440 MB GPL-3.0-derived weights). Actionable finding: an LM layer must be trained on the shipped candidate distribution (incl.ignore_accentsexamples), not just accented gold text. B8 stays open.
- Measured on the new default: SBLGNT 94.7→94.9, AF 90.2→90.7, PROIEL 92.2→93.1, but AGLDT 93.7→93.3 — fails the "never worse" bar for a default; stays opt-in (documented with these numbers).
- Cowen-Breen et al., Machine-Learning Based Detection and Correction of Textual
Errors in Greek Philology (ALP 2023), arXiv:2305.01099;
charliecb/Logion; HFcabrooks/LOGION-*. It is a scribal-error BERT, not a corpus: the 70M-word corpus is request-only, and its models derive from a GPL-3.0 base with no explicit licence — not a clean corpus/LM source. Closed.
- B2: the POS-bigram negative result stands; the learned-emission direction is what B8 measured (the LM is the lever; the bundled perceptron captures most of the transferable signal on accented text). Closed as subsumed by B8.
- B3: robustness/parity work (own round); no accuracy effect — scoping doc stands.
- B5/B6/B7: no
## Accuracyeffect today; carried. - B9: lacuna-restoration pre-pass remains a candidate; the I.PHI part was retired with Round 12.
pytest python/tests— 56 passed.scripts/benchmark.py(shipped defaults) — gate green on the re-baselined thresholds; the four-config matrix is deterministic (independent runs agree).- All measurement material in git-ignored
tmp/; no PROIEL/TLG/PHI-derived content committed.
- B4 remainder (joint-path temperature, POS confidence); B8 revisit with a licence-clean LM trained on the shipped candidate distribution; B3/B5/B6/B7/B9.
- Round 13 remains queued pending the CSNTM contact.
User-selected scope: the PapyGreek recovery pre-pass (spelling variants + name lemmatization) and the annotation-lemma hygiene — the two items that move the top-priority corpus without touching the learned features (that is Round 17).
spelling_variants— bounded (cap 48), closest-first (singles → pairs) orthographic generator: vowel classes ι/ει/η/υ/οι/υι/ου, ε/αι, ο/ω; consonant classes τ/δ/θ, κ/γ/χ, π/β/φ; geminate simplification/doubling; documentary globals ο/οι, ει/ε, ζ/σ, μψ/ψ; final -ν/-ς incl. sandhi (μὲγ→μέν, ἐὰμ→ἐάν); dropped iota subscripts restored on single η positions.name_candidates— case-ending → nominative rules (Δωρίωνος→Δωρίων, Ἀχιλλᾶτος→Ἀχιλλᾶς, Χοινώθμεως→Χοινωθμις, Δωρίωνα→Δωρίων); when the stemlib confirms a reading whose lemma matches the candidate (accents ignored) that reading is used, else a syntheticstemtype="proper"proposal re-accented from the active frequency table and capitalized to match the surface.- Shared
recover()helper — fires only on tokens with zero analyses, batches the re-analysis (2,000/chunk), flagsproposed=True+recovered_from; API, CLI and both measurement harnesses use the same code path. _finishorder: unaccented → numerals → adaptive → recovery → unknown_as_proper → rank → learned (adaptive before recovery so name proposals re-accent from the chosen table; unknown-as-proper after so specific proposals win).- Default flip (B1-style, after measurement):
Morpheus(recovery=True), CLI--no-recovery(positive flag kept as a hidden alias), benchmark--no-recovery. Additive by construction: analyzed tokens are byte-identical with the pre-pass off or on. - 16 tests in
test_recovery.py; 72 total pass.
PapyGreek (36,455 word tokens; learned + adaptive defaults, papyri table,
--numerals), recovery off → on:
| metric | off | on | gain |
|---|---|---|---|
| coverage | 95.1% | 99.2% | +4.1 |
| top-1 ranked (exact / acc-insens) | 85.9 / 87.3 | 87.9 / 89.3 | +2.0 |
| top-1 learned (exact / acc-insens) | 87.9 / 89.4 | 89.7 / 91.2 | +1.8 |
| unanalyzed tokens | 1,781 | 296 | −83% |
- 1,265 distinct forms recovered: 390 spelling-only, 376 name-only, 499 both; exact top-1 hits among recovered tokens split 588 spelling / 134 name.
- Default (no
--numerals): coverage 92.9% → 97.4%; learned 87.5/89.0. - LXX (623,685 tokens): coverage 99.81% → 100.0% (572 forms recovered); learned top-1 unchanged (90.7/91.2) — additive, as designed.
- Coarse POS 80.1% → 79.7% is a completeness artifact: the ~1,485 newly covered tokens enter that metric's denominator and agree at ~70%.
- Remaining PG misses (296) are dominated by marked/truncated forms (189), numerals outside the table (31), stemlib gaps (παστοφόρων, ἐρίδια, πόκια) and the long spelling tail (συνπροσγενόμενος, ἀνήνκασε, γεγράφεκα).
- Gate (SBLGNT / AF / AGLDT / PROIEL): coverage, ranked and learned top-1 up-or-equal on all four (learned SBLGNT 94.67→94.70, AF 90.21→90.26, AGLDT 93.74→93.76, PROIEL 92.19→92.24; coverage +0.02–0.06 each). The analysis rate jumps where misses were name-heavy (AF 98.37%→99.4%, SBLGNT 99.79%→99.95%). The only metric that moves down is AF coarse POS (75.94→75.31) — the documented completeness artifact: ~650 newly analyzed tokens agree at ~15%, and every previously analyzed token's order is byte-identical (the arithmetic reproduces 75.31 exactly).
- Harness
clean_gold_lemmastrips|num:N|/|gap:N|spans from gold lemmas (45 tokens); U+0305 combining overline added to the marker inventory (β̅|num:2|→ β → numeral table). Baseline re-measured under the same harness: learned 87.8/89.3 → 87.9/89.4 (the +0.1 is hygiene, not recovery). - Convention floor documented (data-only): 45 annotated lemmas, 35 hyphenated compound lemmas, 5,598 (15.4%) surface lemmas; degree suppletion (πλεῖστα→πολύς) and particle conventions (κε→ἄν) bound achievable agreement.
- README: new "Recovery pre-pass" section (rules + measurement table + caveats); the coverage limitation bullet updated.
- improvements.md: Round 16 section; PG coverage row 92.9% → 97.4% (default) /
99.2% (
--numerals). - lessons.md: iota-subscript finding; reproduce-through-the-exact-path lesson; re-measure-the-baseline-after-harness-change lesson; POS completeness artifact.
pytest python/tests— 72 passed.- CLI smoke: ἥμυσυ → ἥμισυς (default);
--no-recovery→ miss; hidden--recoveryalias accepted. - Gate: the verification run (recovery on vs the old thresholds) showed
coverage/ranked/learned up-or-equal on all four corpora; its one red line — AF
coarse POS, −0.63pp — is the completeness artifact above (no token's analysis or
order regressed). Thresholds re-baselined with
--updatefrom the shipped-default run; the confirmation re-run was cut off by a session boundary, and Round 17's full gate run re-validates the thresholds end-to-end. - All measurement material in git-ignored
tmp/; no PROIEL/TLG/PHI-derived content committed.
- Round 18 (agreed, deferred): canonical-lemma scoring fix (form-element pollution), learned-feature cleanup, PG retrain with a document-level held-out split.
- B items: B3 (Unicode-native C path), B4 remainder (joint-path temperature + POS
confidence), B5 (profile registry
--base-lemmas), B6 (build determinism), B7 (tokenizer robustness), B8 (licence-clean LM), B9 (lacuna restoration). - Round 13 remains queued pending the CSNTM contact.
Close the measured tail of the Round 16 recovery pre-pass and put PapyGreek — the
priority corpus — into the offline regression gate. Items 17.1–17.6 from the plan
(evidence: the 296-miss Round 16 record in tmp/pg16/).
recovery.spelling_variants rebuilt around primitive edit operations on the
normalized base: every rule is an edit list (_edits returns (rank, pair_eligible, edits)), tier 1 emits all single operations, tier 2 merges every eligible pair
(slot+slot, slot+geminate, slot+global), and the capped list (96) stays closest-first
with vowel substitutions before consonants. Added internal nasal assimilation
(νπ→μπ, νβ→μβ, νφ→μφ, νκ→γκ, νχ→γχ, νγ→γγ) and extended geminate reduction
(ββ γγ δδ θθ φφ χχ beside the existing λλ μμ νν ππ ρρ σσ ττ κκ). Newly reachable:
συνπροσγενόμενος → συμπροσγενόμενος, θρυδάκεια → θριδάκια (υ→ι + ει→ι),
ὑγειαίνησς → ὑγιαίνῃς (ει→ι + σσ→σ).
numerals.propose(beta, surface=None): beside the 50-form table, a vowel-less run of
≥2 numeral letters gets the table's surface-lemma convention with the letter sum in a
value feature (χμγ = 643, ψπ = 780, ρξβ = 162 …); single letters fall back across
case in both directions (the table lacked capital singles like Β, Ζ, Δ, Τ, Φ). Still
opt-in (--numerals), still unanalyzed-only. χμγ's absence from the table was
explained: the builder keeps gold pos m tokens and χμγ's gold pos is x — the rule
covers it without a table rebuild. Ατ (gold 1300, contains a vowel) remains a table
exception (decimal lemma).
build_papyri_stemlib.py gained three curated entries in the generated nom.papyri
(each justified: LSJ headword or LSJ-attested derivative / PG attestation):
παστοφόρος (LSJ via παστοφόριον/ἀρχιπαστοφόρος), πόκιον (PG-attested diminutive of
πόκος), ἐρίδιον (LSJ headword, Dim. of ἔριον, long iota [-ῑδ-]). θριδάκιον and
πιττάκιον were already in lsj.nom, and ἀρχισωματοφύλαξ already analyzed — their
misses were spelling issues (17.1's job). Rebuild: 10 lines added; only
nom.papyri, steminds/nomind, nomind.lindex changed.
scripts/benchmark.py gained the optional PAPYGREEK_DIR corpus: reader mirrors the
tmp harness (original spelling, markers stripped; regularized gold lemma; per-document
grouping; POS classes inverted from the harness's TB2CLASS/MORPH2CLASS maps including
the DC bridge and X/I equivalence), shipped default gated (no --numerals).
Gate row: n=36,455, analyzed 97.4%, cov 90.8%, raw 74.8 → ranked 87.7, learned 88.1,
POS 81.3% (POS matches the harness exactly; ranked/learned read above the harness's
exact column because the gate's matches_lemma credits any lemma variant — see
lessons.md).
Audited (sample: 40 documents, 3,589 tokens; 299 all-proposed tokens): softmax
confidence is conditional on the generated candidate set — lone proposals report 1.0
(63 tokens), mean 0.842 vs ~0.59 measured top-1 accuracy for recovered forms. The
proper fix (fitted per-strategy priors) is carried to B4; findings in
tmp/pg17/audit_17_5.md.
PapyGreek (all 36,455 word tokens; learned + adaptive defaults with the papyri table,
--numerals; Round 16 → Round 17):
| metric | Round 16 | Round 17 |
|---|---|---|
| coverage | 99.2% | 99.4% |
| unanalyzed tokens | 296 | 235 |
| — editorially marked | 189 | 157 |
| — numeral tokens | 31 / 1,454 | 14 / 1,454 |
| top-1 ranked (exact / acc-insens) | 87.9 / 89.3% | 88.1 / 89.5% |
| top-1 learned (exact / acc-insens) | 89.7 / 91.2% | 89.9 / 91.4% |
| recovered forms (spelling/name/both) | 1,265 (390/376/499) | 1,295 (422/331/542) |
| recovered exact top-1 (spelling/name) | 588 / 134 | 634 / 132 |
Default path (no --numerals): 97.4% coverage / 87.6 / 89.1 learned, 942 unanalyzed
(Round 16: 97.4% / 87.5 / 89.0).
ε/η and α/ο interchange experiments: measured against the miss list — ε/η fixes 3 tokens, α/ο 6; both add tier-1 operations that shift every existing target deeper into the capped list, so both were deferred with the measurement recorded.
- README: recovery section (op-based generator, new rules, Round 16/17 table); numerals section (compositional rule, symmetric case fallback); limitation bullets.
- improvements.md: Round 17 section; PG opt-in coverage row 99.2% → 99.4%.
- lessons.md: op-based generation; measure-a-symmetric-rule; builder filters; gate vs harness comparison conventions; confirmation-runs; proposal-confidence audit.
pytest python/tests— 75 passed.- CLI smoke: συνπροσγενόμενος/θρυδάκεια/ὑγειαίνησς recovered (default); χμγ → χμγ
[value=643], ψπ → ψπ [value=780] with
--numerals. - Gate: thresholds re-baselined with
--updateon all five corpora (shipped default). SBLGNT unchanged on every metric; AF analyzed +0.01; AGLDT unchanged except learned −0.01pp (the recovery's new variants alter a few previously-recovered tokens' candidate sets; the three new stems match no AGLDT token); PROIEL +0.01/+0.02; PapyGreek joined the gate (n=36,455, analyzed 97.42%, cov 90.81%, ranked 87.73%, learned 88.07%, POS 81.28%). The confirmation run reproduced every metric byte-identically and reported OK (within tolerance). - All measurement material in git-ignored
tmp/; no PROIEL/TLG/PHI-derived content committed.
- B4: fitted per-strategy priors for proposal confidence.
- ε/η + α/ο interchanges: measured, deferred (9 tokens; cap-pressure risk).
- Round 18 unchanged (canonical-lemma scoring + learned retrain).
Fix the candidate-scoring primitive that credited a form element with another word's frequency (18.1), clean up the learned features that encoded it (18.2), and retrain the learned model on the shipped candidate distribution with a document-level PapyGreek held-out split (18.3); gate + docs (18.4). The B items B4/B5/B7 close in the same session (B6 was already committed); B3/B8/B9 are assessed and carried.
Morpheus's form,lemma candidate field carries the forms the stem matched before the
lemma; a Doric/alternate stem can make one entry's form element equal a different
word's lemma — πηρός's Doric stem lists παρά, so variant-list scoring credited πηρός
with παρά's frequency (366) and promoted it above the actual preposition on PapyGreek.
Ranker.frequency_score now scores normalize_lemma(analysis.lemma) only; the same
fix applies to LearnedRanker._best_frequency/_best_joint, the adaptive table fit
(tables.py) and the opt-in homograph re-ranker (homographs.py). Matching still
considers every variant (Analysis.matches_lemma, the Strong's fallback, the gold
readers) — matching wants recall, scoring wants identity.
Measured (same harness, before → after): PG plain path ranked 88.1 → 89.4 exact /
89.5 → 90.8 accent-insensitive; recovered-form top-1 634 → 660; SBLGNT a wash
(top-1 ranked 94.6, POS 77.9 = the gate baseline). Evidence: tmp/r18/pg_18_1.txt,
tmp/r18/sblgnt_18_1.txt.
The frequency/rank features were computed from the polluted ordering, so 18.1 fixed
them at the source; all scoring sites were re-verified (grep variants() now finds
only matching/presentation uses). No proposed feature was added: proposal membership
is constant within a token's candidate set, so it cancels in perceptron updates and
cannot affect the ranking.
scripts/build_learned_model.py now (a) runs the same recovery pre-pass the shipped
pipeline uses — tokens with no analysis become spelling/name proposals and are ranked
like any other candidate; (b) reads the PapyGreek treebanks (34,998 tokens) with a
document-level stride split — 79 documents / 7,474 tokens held out; (c) prefers
canonical-lemma matches when choosing the gold index (variants fallback); (d) reports
the held-out frequency-vs-learned top-1 and stores it in the model file
(papygreek_holdout).
Training: 411,615 analyzable tokens (Round 15: 385,758), 827 features → 711 kept (≥2 occurrences). Held-out PapyGreek (6,936 analyzable of 7,474): frequency top-1 94.2%, learned top-1 96.1% (training convention: covered tokens, bundled Koine table). Model source string updated: "+ PapyGreek Treebanks v1.01 (CC-BY-SA 4.0)".
PapyGreek with the new model (all 36,455 word tokens; learned + adaptive defaults with
the papyri table, --numerals; Round 17 → Round 18):
| metric | Round 17 | Round 18 |
|---|---|---|
| coverage | 99.4% | 99.4% |
| unanalyzed tokens | 235 | 235 |
| top-1 ranked (exact / acc-insens) | 88.1 / 89.5% | 89.4 / 90.8% |
| top-1 learned (exact / acc-insens) | 89.9 / 91.4% | 90.2 / 91.7% |
| POS coarse | 81.1% | 81.1% |
| recovered-form top-1 (ranked, exact) | 634 | 660 |
default path (no --numerals) learned |
87.6 / 89.1% | 88.0 / 89.5% |
Full offline gate re-run with the round's defaults (adaptive + learned + recovery),
re-baselined with --update and confirmed green against the new thresholds. Every
per-corpus adaptive table: pick is unchanged (SBLGNT→koine, Apostolic Fathers→
apostolic, AGLDT→classical, PROIEL→koine, PapyGreek→papyri) — the check that the
scoring fix did not move a load-bearing table choice — and no metric regressed:
| corpus | coverage | ranked (17 → 18) | learned (17 → 18) | POS (17 → 18) |
|---|---|---|---|---|
| SBLGNT | 95.3% | 94.60 → 94.60 | 94.70 → 94.70 | 77.82 → 78.06 |
| Apostolic Fathers | 91.3% | 90.52 → 90.49 | 90.26 → 90.21 | 75.31 → 75.54 |
| AGLDT classical | 98.7% | 92.80 → 92.77 | 93.75 → 93.84 | 85.19 → 85.95 |
| PROIEL greek-nt | 94.2% | 91.71 → 91.75 | 92.25 → 92.10 | 81.53 → 81.74 |
| PapyGreek | 90.8% | 87.73 → 87.71 | 88.07 → 88.25 | 81.28 → 82.75 |
The PapyGreek ranked gate number is flat because the gate's comparison credits a candidate whose variant matches the gold: the polluted candidates the fix demotes (πηρός with form element παρά) already counted as correct there, so the +1.3pp shows in the harness's exact column, not in this one. POS agreement improved on every corpus (+1.5pp on PapyGreek); the retrained model moves ≤0.15pp on the NT-family corpora. README tables re-measured (learned, adaptive-vs-fixed, PapyGreek section, recovery-off baseline 95.2% / 87.2% / 1,750 unanalyzed).
- Joint-path temperature
temperature_joint= 0.12 (SBLGNT ECE 0.198 → 0.137, AF 0.192 → 0.133) — the joint path genuinely needs its own temperature (plain 0.08, learned 1.03). - POS confidence: the class-mass estimate is peaked for the lemma, not for the
class (mass ECE 0.19); the reported
pos_confidencenow multiplies the class mass by a per-class reliability fitted on SBLGNT — learned path ECE 0.19 → 0.002, frequency 0.19 → 0.021, joint 0.19 → 0.014 (AF transfer 0.003 / 0.020 / 0.016). The fitted rates make the Morpheus↔reference convention gaps explicit: when Morpheus saysAthe reference usually saysR(determiners αὐτός/οὗτος areAhere,RD/RPthere; 0.04), and itsXparticles are usually referenceCconjunctions (0.07); D (adverb/particle) is 0.48-0.63.pos_reliabilityis stored per score path with a plain-table fallback. - Proposal priors:
proposal_priorsfitted through the shipped API path on PapyGreek scales the conditional softmax of all-proposed tokens, so a lone proposal reports its strategy's measured top-1 rate instead of 1.0 — spelling 0.49 (1,019/2,078), name 0.27 (123/455), fallbackother0.45. Measured end-to-end:μοὐ's three spelling proposals report 0.490/0/0 and the top'spos_confidenceis 0.412 (mass × C-reliability).
profiles.py gained BaseLemmas: compound verbs lemmatize to the base verb, a preverb
is stripped only when the remainder resolves as an attested verb in the analyzer's
joint (lemma, POS) table (falling back to the bundled Koine table), and contracted
compounds are recovered by tail-matching guarded on attested preverb remnants. Profiles
are comma-combinable (--profile morphgnt,base-lemmas, applied left to right) and the
CLI validates names. This is the analyzer-guarded mode build_morph_lxx.py --base-lemmas used.
The five STEMPROG helper binaries and tailnolen (an Alpha COFF binary with no source
or references) are untracked and git-ignored; a rebuild was verified against the
tracked outputs with the SDK/CFLAGS environment from build-stemlib.sh. A full make clean still regenerates the tracked endtables/steminds (pre-existing, out of scope).
tokenize() now normalizes editorial input instead of mangling it: NFC normalization
(NFD accents no longer split tokens), |annotation| spans removed (πα|gap:1|ρά no
longer leaked gap as a token), editorial combining marks (dot-below, overline)
removed, bracket/ellipsis/hyphen runs between letters removed (ἀδελφ[οῦ] is one
token), and elision/crasis apostrophes (ʼ ’ ' koronis) stay attached because Morpheus
analyses the elided forms (ἀλλʼ → ἀλλά, δʼ → δέ). Crasis stays a single token: the
stemlib analyses the common forms (κἀγώ → ἐγώ, τοὐμόν → ἐμός, τἀληθές → ἀληθής, χὠ → ὁ)
and the rare ones go through recovery (μοὐ → μοι); the contracted vowel cannot be
restored without a lexicon.
- B3 (Unicode-native C input path): scoping doc exists
(
docs/unicode-native-input.md); a robustness/parity refactor of the cruncher's input path with no accuracy effect — its own round, with dedicated parity validation against the NT-output-invariance rule. Carried. - B8 (licence-clean LM disambiguation layer): the Round 15 head-to-head stands —
the LM-MLP wins on accented text (SBLGNT 99.4 vs 97.9 ambiguous-only; AF 98.0 vs
97.3) but loses on the shipped accent-insensitive pipeline (PROIEL 92.3 vs 96.4;
ignore_accentsinflates the multi-candidate rate 37.8% → 59.4%), and it needs torch plus a GPL-3.0-derived ~440 MB checkpoint. A shippable version means training a model on the shipped candidate distribution (includingignore_accentsexamples) — a GPU-scale task. Carried with that concrete next step. - B9 (lacuna-restoration pre-pass): needs external model weights (Ithaca/Pythia) and PHI-derived text (tmp-only); deferred.
- CSNTM outreach (Round 13) stays blocked on user contact.
- ε/η and α/ο interchange evidence stays in lessons.md (deferred).
- Ατ (gold 1300, decimal lemma) remains a documented PG numeral residual.
Round 19: PG error audit, adaptive numerals, Unicode-native C input, MLP head-to-head (2026-10-16) ✅
User direction "do all 5" — the five suggestions from the Round 18 wrap-up:
(19.1) a PapyGreek top-1 error audit that names what is actually left and bounds
what ranking work can buy; (19.2) register-aware numerals
(numerals="auto" / --numerals-auto); (19.3) the B8-lite CPU contextual
re-ranker, head-to-head against the perceptron under a ship/no-ship gate;
(19.4) the B3 Unicode-native cruncher input path with exhaustive parity
validation; (19.5) CSNTM demo refresh + outreach email to unblock Round 13.2.
Records (19.6) close the round.
All 36,455 PG word tokens through the shipped path: accent-insensitive
candidate pass, recovery pre-pass, learned re-ranking, the adaptive papyri
frequency table (selection score 0.83), numerals off. Script
tmp/r19/audit_pg_top1.py; evidence tmp/r19/audit_19_1.log,
audit_19_1_errors.tsv (2,857 error tokens), audit_19_1_families.tsv
(1,510 families), report tmp/r19/audit_19_1_report.md.
Faithfulness: learned top-1 exact/acc-insensitive 88.1 / 89.6%, reproducing the Round 18 default path (88.0 / 89.5). Cross-check: the default Koine table instead of the adaptive papyri pick gives 85.6 / 88.2 — the adaptive selection is worth ~2.5pp exact on PG, and the audit uses the shipped configuration.
| bucket | tokens | share |
|---|---|---|
| covered (≥1 candidate) | 35,513 | 97.4% |
| misses | 942 | 2.58% |
| gold lemma absent from candidates | 1,725 | 4.73% |
| gold present, not top-1 (ranking) | 1,132 | 3.11% |
| top-1 exact | 32,105 | 88.1% |
| top-1 accent-insensitive | 32,656 | 89.6% |
- Misses (942). 717 are the numeral/indeclinable class; 703 are covered by
--numerals/--numerals-auto(14 remain: decimal-annotated gold like Ατ, sub-threshold forms). ~225 non-numeral ≈ the Round 18 residual (157 editorially marked or truncated). - Gold absent (1,725). 192 are covered-misanalyzed numerals (the token has a wrong reading, so the misses-only numerals path cannot fire): ιθ→ηθ 65, κε→ἄν 27, ιζ→εἰμί/ἴς 16, ιδ→ηθ 12, λε→ληίς 11, ιγ→ἐν 16…; a numeral proposal exists for 134 (vowel-less rule or table). The other 1,533 are editorial/regularization conventions — the treebank's regularized lemma is not the lemma of the spelling present (ποιήσις→ποιέω 11, πλεῖστα→πολύς 8, ἐπὶ→ἐπεί 20, εἵνα→ἵνα 19, κὲ→καί 14, Ἡρακλέους→ἡρακλέης 10, καθάπερ→καθά 11). Candidate-generation work, not ranking; corpus-specific lemma mapping was declined in Rounds 12/17 — do not chase.
- Gold present (1,132, the only ranking-fixable bucket). 925 native candidates (443 gold at rank 2; POS-class agrees 366; both lemmas common 319), including numeral-gold with the numeral candidate present — 140 (ιη→κερατέα 51, κη→πη 32, ια→κερατέα 23, ιε→ἰός/εἶμι 17); 147 recovery proposals out-ranking a native gold reading; 60 variant-credited (gate hits). Largest families: function-word homographs ἀλλά→ἄλλος 86, ὅς→ὁ 69+ (plus ὃ/ἃ/ἃς), εἰμί→ἐάν/οὖν/ ὄντα/εἰ 43, πᾶς→πάντῃ 24, σύ→σου/σε 13, ὡς→ὅς 14+; word-formation confusions ἀποδίδωμι→ἄπους 28 (ἀπόδος), χαλκοῦς→χαλκός 18, ἀξιόω→ἄξιος 11, ἀντίγραφος→ἀντιγράφω 9, ἀπαντάω→ἀπαντή 9.
- Ceilings. Better ranking with no candidate changes: +3.1pp (89.6 → 92.7 acc-insensitive, numerals off). A papyri-scoped numeral-candidate rule (192 + 140 ≈ 274 tokens) is worth ~+0.75pp but needs its own round with a false-positive audit (κέ/τ/abbreviations). B8-lite's honest target was therefore the 1,132-token bucket — fractions of a point, not points.
The papyrus-numeral proposals (Round 14) now have a register-aware form: apply
them only when adaptive table selection picked the papyri table for this
input, so numeral-shaped misses in other registers stay misses. api.py
accepts numerals: bool | "auto" (validated; True keeps the old
always-on behavior); the auto pass runs after _apply_adaptive_tables — so
numeral proposals can never skew the table selection that gates them — and
_selected_table records the pick. It needs selection to run: caller-supplied
frequencies or adaptive_tables=False leave it inert (documented).
CLI: --numerals-auto (mutually exclusive with --numerals). Implemented as a
distinct flag rather than the planned --numerals=auto optional-value form,
because nargs="?" would make --numerals file.txt eat the positional path
argument — recorded in lessons.md.
Tests: 5 new (test_api.py): auto fires on PG text; silent on SBLGNT-shaped
text even with numeral misses; explicit numerals=True unchanged; inert
without adaptive selection; invalid string rejected. 105 tests pass.
Verified: an 8-document PG sample selects papyri; bgu.3.948.xml through the
full shipped path (learned + recovery) gives auto output identical to explicit
numerals=True; SBLGNT selects koine and auto output is identical to
numerals=False. Equivalence on the other corpora is by construction (auto is
the same code path once the selection matches).
scripts/build_learned_model.py gained an opt-in --mlp branch
(train_mlp(); graceful without torch): listwise softmax cross-entropy over
each token's candidates, bucketed by candidate count, batch 128, Adam 1e-3,
best-held-out state, pure-Python payload shape (feature_index/w1/b1/
w2/b2, type "mlp") so shipping would have needed only a forward path.
Run on the same shipped candidate distribution as the Round 18 retrain (recovery pre-pass included): 411,615 analyzable tokens, 827 features → 815 kept; PG document-level held-out split (6,936 analyzable of 7,474).
| model | held-out top-1 |
|---|---|
| frequency baseline | 94.22% |
| perceptron (shipped) | 96.08% |
| MLP (64 hidden, 8 epochs, best) | 96.29% |
The perceptron reproduces the shipped model byte-identically (weights and
feature index equal — the training pipeline is fully reproducible). The MLP's
+0.21pp (≈15 held-out tokens; SE ≈ 0.24pp at n = 6,936) is below the pre-set
≥0.3pp ship gate and within noise, so it is not shipped: nonlinearity
over the existing features buys fractions of a point, exactly as 19.1
predicted. The MLP improved monotonically over its 8 epochs (95.95 → 96.29),
so the carried next step is features (the audit's function-word homograph
families and context counts), not more model capacity. Evidence:
tmp/r19/train_r19.log, learned_reranker_r19.json,
learned_reranker_r19_mlp.json.
cruncher -u converts UTF-8 Greek directly to lowercase beta in C before the
existing pipeline (src/anal/gkunicode.c; -u wired into stdiomorph.c,
Greek-only — Latin keeps its case). The converter replicates the beta-code
package's algorithm — NFC normalize → per-codepoint map → pass-through →
lowercase — with every table generated from the same
unicode_to_beta_code.json the Python converter uses
(scripts/gen_gkunicode_tables.py → src/anal/gkunicode_tab.c: 271 map
entries, 449 composition pairs, 111 combining classes, 153 lowercase mappings,
259 NFD expansions). bin/gkconvert is the oracle binary; makefile links
gkunicode.o gkunicode_tab.o into cruncher and gkconvert; the binary is
git-ignored.
Parity (scripts/unicode_parity.py): the exhaustive synthetic set (map keys,
both Greek ranges, pair compositions in NFC and NFD spellings, mark orders,
apostrophes, 3K random strings) plus every distinct form in MorphGNT, AF,
AGLDT, PROIEL and PapyGreek — 110,927 forms, 0 mismatches against
to_beta(w).lower(); conversion speed ~2M forms/s. cruncher -u output is
byte-identical to the beta-input path on samples; default (no -u) behavior
unchanged. Guards: python/tests/test_unicode_c_path.py (synthetic oracle +
direct conversions + cruncher -u vs beta path; skipped when not built).
Debugging found three real divergences the oracle caught: singleton NFD
decompositions (U+0340/0343/0344) are not composition pairs (367 forms), spacing
decomposition pieces (U+00A8/backtick/space) must start recomposition (16
forms), and a generated-table struct overflow (nfd[4] + terminator) produced
a 1,134-form output explosion. docs/unicode-native-input.md now documents the
implemented design; README gained the -u row + example; lessons recorded.
The toolkit keeps the Python to_beta path for now — switching it to -u is
a later decision, gated on this parity (now proven).
docs/csntm-morphology-demo.md refreshed: current measured numbers (SBLGNT
94.6 / 94.7 exact/acc-insensitive; PG 89.4 / 90.8 with numerals; learned ~+2
over frequency on the held-out split), the John 1:1–2 worked example re-run on
the current build (24/24 analyzed; ἐν 0.99, ἀρχῇ 1.00, ἦν 0.88 with the
calibrated confidences), and the JSONL excerpt updated to the current record
shape (lemma_variants, pos_confidence, recovered_from). The honest
uncial-text nuance section stands (accent-insensitive strips accents but not
breathings; bare forms can mis-resolve). docs/csntm-outreach.md drafts the
email for the user to send — the last step of Round 13.2.
Full offline gate re-run in verification mode (tmp/r19/refresh_gate.sh --check, five corpora in parallel, against the committed thresholds): all
five OK (within tolerance) — SBLGNT 94.6/94.7, AF 90.5/90.2, AGLDT 92.8/93.8,
PROIEL 91.8/92.1, PapyGreek 87.7/88.2 — and every adaptive pick unchanged
(SBLGNT→koine, AF→apostolic, AGLDT→classical, PROIEL→koine, PG→papyri). As
expected: the round's changes are additive (-u, --numerals-auto),
measurement-only (the audit), or outside the shipped model (the perceptron
reproduces the shipped file byte-identically, so no model was re-shipped).
105 pytest pass.
Records: this entry; improvements.md Round 19 section; lessons (audit under
the shipped configuration; split gold-absent from gold-present before proposing
ranking work; a gated pass must not run before its gate; distinct flag over
optional-value flag; replicate the reference converter's algorithm; NFC =
NFD + recomposition; composition starters include decomposition pieces;
terminator slot sizing; greedy hex escapes; exhaustive oracle; test the
evaluation path with held-out data before the long run).
- Round 13.2: send the CSNTM email (user action; draft ready).
- Numeral-candidate rule for covered-misanalyzed numerals (~274 tokens, ~+0.75pp) — own round, false-positive audit required (κέ/τ/abbreviations).
- Context/homograph ranking (the +3.1pp ceiling): the audit's families (ἀλλά/ἄλλος, ὅς/ὁ, εἰμί/ἐάν, πᾶς/πάντῃ); B8-lite showed model capacity is not the lever — features/data are.
- B3 toolkit switch-over (use
-uin the runner; keepto_betafor the APIbetafield) — parity is proven, decision deferred. - Ατ (gold 1300, decimal lemma) remains a documented PG numeral residual.