Add morphology registry for language packs
This commit is contained in:
parent
a4b4d22987
commit
8d09c2a8c1
11 changed files with 196 additions and 16 deletions
|
|
@ -142,4 +142,18 @@ def load_pack_entries(pack_id: str) -> list[LexicalEntry]:
|
|||
for line in lexicon_path.read_text(encoding="utf-8").splitlines():
|
||||
if line.strip():
|
||||
entries.append(_parse_entry(json.loads(line)))
|
||||
_validate_morphology_links(pack_id, entries)
|
||||
return entries
|
||||
|
||||
|
||||
def _validate_morphology_links(pack_id: str, entries: list[LexicalEntry]) -> None:
|
||||
morphology_ids = [entry.morphology_id for entry in entries if entry.morphology_id]
|
||||
if not morphology_ids:
|
||||
return
|
||||
|
||||
from morphology.registry import load_morphology
|
||||
|
||||
registry = load_morphology(pack_id)
|
||||
missing = [morphology_id for morphology_id in morphology_ids if registry.get(morphology_id) is None]
|
||||
if missing:
|
||||
raise ValueError(f"{pack_id}: dangling morphology_id link(s): {', '.join(missing)}")
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{"entry_id": "grc-001", "surface": "\u03bb\u03cc\u03b3\u03bf\u03c2", "lemma": "\u03bb\u03cc\u03b3\u03bf\u03c2", "language": "grc", "pos": "NOUN", "semantic_domains": ["logos.utterance.word", "logos.core", "communication.articulation", "divine.revelation", "existence.utterance"], "morphology_tags": ["noun", "masculine", "nominative", "singular", "second_declension", "root:\u03bb\u03b5\u03b3"]}
|
||||
{"entry_id": "grc-002", "surface": "\u1f00\u03c1\u03c7\u03ae", "lemma": "\u1f00\u03c1\u03c7\u03ae", "language": "grc", "pos": "NOUN", "semantic_domains": ["logos.genesis.origin", "logos.core", "origin", "existence.ground", "temporal.absolute"], "morphology_tags": ["noun", "feminine", "nominative", "singular", "first_declension", "root:\u1f00\u03c1\u03c7"]}
|
||||
{"entry_id": "grc-003", "surface": "\u03c6\u1ff6\u03c2", "lemma": "\u03c6\u1ff6\u03c2", "language": "grc", "pos": "NOUN", "semantic_domains": ["logos.illumination.photon", "logos.core", "logos.illumination", "divine.revelation", "knowledge.clarity"], "morphology_tags": ["noun", "neuter", "nominative", "singular", "third_declension", "root:\u03c6\u03c9\u03c4"]}
|
||||
{"entry_id": "grc-004", "surface": "\u03b6\u03c9\u03ae", "lemma": "\u03b6\u03c9\u03ae", "language": "grc", "pos": "NOUN", "semantic_domains": ["logos.vitality.animate", "logos.core", "vitality", "existence.manifestation", "being.animate"], "morphology_tags": ["noun", "feminine", "nominative", "singular", "first_declension", "root:\u03b6\u03b1"]}
|
||||
{"entry_id": "grc-005", "surface": "\u1f00\u03bb\u03ae\u03b8\u03b5\u03b9\u03b1", "lemma": "\u1f00\u03bb\u03ae\u03b8\u03b5\u03b9\u03b1", "language": "grc", "pos": "NOUN", "semantic_domains": ["logos.aletheia.verity", "logos.core", "epistemic.ground", "knowledge.ground", "divine.revelation"], "morphology_tags": ["noun", "feminine", "nominative", "singular", "first_declension", "alpha_privative", "root:\u03bb\u03b1\u03b8"]}
|
||||
{"entry_id": "grc-006", "surface": "\u03c0\u03bd\u03b5\u1fe6\u03bc\u03b1", "lemma": "\u03c0\u03bd\u03b5\u1fe6\u03bc\u03b1", "language": "grc", "pos": "NOUN", "semantic_domains": ["logos.pneuma.breath", "logos.core", "breath", "existence.animating", "logos.presence"], "morphology_tags": ["noun", "neuter", "nominative", "singular", "third_declension", "root:\u03c0\u03bd\u03b5\u03c5"]}
|
||||
{"entry_id": "grc-007", "surface": "\u03ba\u03c4\u03af\u03b6\u03c9", "lemma": "\u03ba\u03c4\u03af\u03b6\u03c9", "language": "grc", "pos": "VERB", "semantic_domains": ["logos.ktizo.formation", "logos.core", "formation", "logos.genesis", "divine.agency"], "morphology_tags": ["verb", "present", "active", "indicative", "first_person", "singular", "root:\u03ba\u03c4\u03b9\u03b4"]}
|
||||
{"entry_id": "grc-001", "surface": "\u03bb\u03cc\u03b3\u03bf\u03c2", "lemma": "\u03bb\u03cc\u03b3\u03bf\u03c2", "language": "grc", "pos": "NOUN", "morphology_id": "grc-morph-001", "semantic_domains": ["logos.utterance.word", "logos.core", "communication.articulation", "divine.revelation", "existence.utterance"], "morphology_tags": ["noun", "masculine", "nominative", "singular", "second_declension", "root:\u03bb\u03b5\u03b3"]}
|
||||
{"entry_id": "grc-002", "surface": "\u1f00\u03c1\u03c7\u03ae", "lemma": "\u1f00\u03c1\u03c7\u03ae", "language": "grc", "pos": "NOUN", "morphology_id": "grc-morph-002", "semantic_domains": ["logos.genesis.origin", "logos.core", "origin", "existence.ground", "temporal.absolute"], "morphology_tags": ["noun", "feminine", "nominative", "singular", "first_declension", "root:\u1f00\u03c1\u03c7"]}
|
||||
{"entry_id": "grc-003", "surface": "\u03c6\u1ff6\u03c2", "lemma": "\u03c6\u1ff6\u03c2", "language": "grc", "pos": "NOUN", "morphology_id": "grc-morph-003", "semantic_domains": ["logos.illumination.photon", "logos.core", "logos.illumination", "divine.revelation", "knowledge.clarity"], "morphology_tags": ["noun", "neuter", "nominative", "singular", "third_declension", "root:\u03c6\u03c9\u03c4"]}
|
||||
{"entry_id": "grc-004", "surface": "\u03b6\u03c9\u03ae", "lemma": "\u03b6\u03c9\u03ae", "language": "grc", "pos": "NOUN", "morphology_id": "grc-morph-004", "semantic_domains": ["vitality.animate", "vitality", "existence.manifestation", "being.animate"], "morphology_tags": ["noun", "feminine", "nominative", "singular", "first_declension", "root:\u03b6\u03b1"]}
|
||||
{"entry_id": "grc-005", "surface": "\u1f00\u03bb\u03ae\u03b8\u03b5\u03b9\u03b1", "lemma": "\u1f00\u03bb\u03ae\u03b8\u03b5\u03b9\u03b1", "language": "grc", "pos": "NOUN", "morphology_id": "grc-morph-005", "semantic_domains": ["logos.aletheia.verity", "logos.core", "epistemic.ground", "knowledge.ground", "divine.revelation"], "morphology_tags": ["noun", "feminine", "nominative", "singular", "first_declension", "alpha_privative", "root:\u03bb\u03b1\u03b8"]}
|
||||
{"entry_id": "grc-006", "surface": "\u03c0\u03bd\u03b5\u1fe6\u03bc\u03b1", "lemma": "\u03c0\u03bd\u03b5\u1fe6\u03bc\u03b1", "language": "grc", "pos": "NOUN", "morphology_id": "grc-morph-006", "semantic_domains": ["logos.pneuma.breath", "logos.core", "breath", "existence.animating", "logos.presence"], "morphology_tags": ["noun", "neuter", "nominative", "singular", "third_declension", "root:\u03c0\u03bd\u03b5\u03c5"]}
|
||||
{"entry_id": "grc-007", "surface": "\u03ba\u03c4\u03af\u03b6\u03c9", "lemma": "\u03ba\u03c4\u03af\u03b6\u03c9", "language": "grc", "pos": "VERB", "morphology_id": "grc-morph-007", "semantic_domains": ["logos.ktizo.formation", "logos.core", "formation", "logos.genesis", "divine.agency"], "morphology_tags": ["verb", "present", "active", "indicative", "first_person", "singular", "root:\u03ba\u03c4\u03b9\u03b4"]}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
"normalization_policy": "unitize_versor",
|
||||
"source_manifest": "grc_logos_micro_v1.lexicon.jsonl",
|
||||
"determinism_class": "D0",
|
||||
"checksum": "1fea9d9c775d49a1dd17857c240afcd60e22c625e10601aa90e22b10d9e70262",
|
||||
"checksum": "462d0aee88714f04e003d0a3f9d9015f460ba39657c4b8dff8dfd2ed24d75a3f",
|
||||
"version": "1.0.0",
|
||||
"gate_engaged": true,
|
||||
"oov_policy": "fail_closed"
|
||||
|
|
|
|||
7
language_packs/data/grc_logos_micro_v1/morphology.jsonl
Normal file
7
language_packs/data/grc_logos_micro_v1/morphology.jsonl
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
{"morphology_id": "grc-morph-001", "surface": "\u03bb\u03cc\u03b3\u03bf\u03c2", "lemma": "\u03bb\u03cc\u03b3\u03bf\u03c2", "language": "grc", "root": "\u03bb\u03b5\u03b3", "prefix_chain": [], "stem": "\u03bb\u03bf\u03b3", "inflection": {"pos": "noun", "case": "nominative", "gender": "masculine", "number": "singular", "declension": "second"}, "suffix_chain": ["\u03bf\u03c2"]}
|
||||
{"morphology_id": "grc-morph-002", "surface": "\u1f00\u03c1\u03c7\u03ae", "lemma": "\u1f00\u03c1\u03c7\u03ae", "language": "grc", "root": "\u1f00\u03c1\u03c7", "prefix_chain": [], "stem": "\u1f00\u03c1\u03c7", "inflection": {"pos": "noun", "case": "nominative", "gender": "feminine", "number": "singular", "declension": "first"}, "suffix_chain": ["\u03ae"]}
|
||||
{"morphology_id": "grc-morph-003", "surface": "\u03c6\u1ff6\u03c2", "lemma": "\u03c6\u1ff6\u03c2", "language": "grc", "root": "\u03c6\u03c9\u03c4", "prefix_chain": [], "stem": "\u03c6\u03c9\u03c4", "inflection": {"pos": "noun", "case": "nominative", "gender": "neuter", "number": "singular", "declension": "third"}, "suffix_chain": ["\u03c2"]}
|
||||
{"morphology_id": "grc-morph-004", "surface": "\u03b6\u03c9\u03ae", "lemma": "\u03b6\u03c9\u03ae", "language": "grc", "root": "\u03b6\u03b1", "prefix_chain": [], "stem": "\u03b6\u03c9", "inflection": {"pos": "noun", "case": "nominative", "gender": "feminine", "number": "singular", "declension": "first"}, "suffix_chain": ["\u03ae"]}
|
||||
{"morphology_id": "grc-morph-005", "surface": "\u1f00\u03bb\u03ae\u03b8\u03b5\u03b9\u03b1", "lemma": "\u1f00\u03bb\u03ae\u03b8\u03b5\u03b9\u03b1", "language": "grc", "root": "\u03bb\u03b1\u03b8", "prefix_chain": ["\u1f00"], "stem": "\u03bb\u03b7\u03b8\u03b5\u03b9", "inflection": {"pos": "noun", "case": "nominative", "gender": "feminine", "number": "singular", "declension": "first"}, "suffix_chain": ["\u03b1"]}
|
||||
{"morphology_id": "grc-morph-006", "surface": "\u03c0\u03bd\u03b5\u1fe6\u03bc\u03b1", "lemma": "\u03c0\u03bd\u03b5\u1fe6\u03bc\u03b1", "language": "grc", "root": "\u03c0\u03bd\u03b5\u03c5", "prefix_chain": [], "stem": "\u03c0\u03bd\u03b5\u03c5\u03bc\u03b1\u03c4", "inflection": {"pos": "noun", "case": "nominative", "gender": "neuter", "number": "singular", "declension": "third"}, "suffix_chain": []}
|
||||
{"morphology_id": "grc-morph-007", "surface": "\u03ba\u03c4\u03af\u03b6\u03c9", "lemma": "\u03ba\u03c4\u03af\u03b6\u03c9", "language": "grc", "root": "\u03ba\u03c4\u03b9\u03b4", "prefix_chain": [], "stem": "\u03ba\u03c4\u03b9\u03b6", "inflection": {"pos": "verb", "tense": "present", "voice": "active", "mood": "indicative", "person": "first", "number": "singular"}, "suffix_chain": ["\u03c9"]}
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
{"entry_id": "he-001", "surface": "\u05d3\u05d1\u05e8", "lemma": "\u05d3\u05d1\u05e8", "language": "he", "pos": "NOUN", "semantic_domains": ["logos.utterance.word", "logos.core", "communication.articulation", "divine.revelation", "existence.utterance"], "morphology_tags": ["noun", "masculine", "singular", "root:\u05d3\u05d1\u05e8", "triliteral:D-B-R"]}
|
||||
{"entry_id": "he-002", "surface": "\u05e8\u05d0\u05e9\u05d9\u05ea", "lemma": "\u05e8\u05d0\u05e9\u05d9\u05ea", "language": "he", "pos": "NOUN", "semantic_domains": ["logos.genesis.origin", "logos.core", "origin", "existence.ground", "temporal.absolute"], "morphology_tags": ["noun", "feminine", "singular", "root:\u05e8\u05d0\u05e9", "construct_state"]}
|
||||
{"entry_id": "he-003", "surface": "\u05d0\u05d5\u05e8", "lemma": "\u05d0\u05d5\u05e8", "language": "he", "pos": "NOUN", "semantic_domains": ["logos.illumination.photon", "logos.core", "logos.illumination", "divine.revelation", "knowledge.clarity"], "morphology_tags": ["noun", "masculine", "singular", "root:\u05d0\u05d5\u05e8", "triliteral:A-W-R"]}
|
||||
{"entry_id": "he-004", "surface": "\u05d7\u05d9\u05d9\u05dd", "lemma": "\u05d7\u05d9\u05d9\u05dd", "language": "he", "pos": "NOUN", "semantic_domains": ["logos.vitality.animate", "logos.core", "vitality", "existence.manifestation", "being.animate"], "morphology_tags": ["noun", "masculine", "plural", "root:\u05d7\u05d9\u05d4", "triliteral:H-Y-H"]}
|
||||
{"entry_id": "he-005", "surface": "\u05d0\u05de\u05ea", "lemma": "\u05d0\u05de\u05ea", "language": "he", "pos": "NOUN", "semantic_domains": ["logos.aletheia.verity", "logos.core", "epistemic.ground", "knowledge.ground", "divine.revelation"], "morphology_tags": ["noun", "feminine", "singular", "root:\u05d0\u05de\u05df", "triliteral:A-M-N"]}
|
||||
{"entry_id": "he-006", "surface": "\u05e8\u05d5\u05d7", "lemma": "\u05e8\u05d5\u05d7", "language": "he", "pos": "NOUN", "semantic_domains": ["logos.pneuma.breath", "logos.core", "breath", "existence.animating", "logos.presence"], "morphology_tags": ["noun", "feminine", "singular", "root:\u05e8\u05d5\u05d7", "triliteral:R-W-H"]}
|
||||
{"entry_id": "he-007", "surface": "\u05d1\u05e8\u05d0", "lemma": "\u05d1\u05e8\u05d0", "language": "he", "pos": "VERB", "semantic_domains": ["logos.ktizo.formation", "logos.core", "formation", "logos.genesis", "divine.agency"], "morphology_tags": ["verb", "qal", "perfect", "third_person", "masculine", "singular", "root:\u05d1\u05e8\u05d0", "triliteral:B-R-A"]}
|
||||
{"entry_id": "he-001", "surface": "\u05d3\u05d1\u05e8", "lemma": "\u05d3\u05d1\u05e8", "language": "he", "pos": "NOUN", "morphology_id": "he-morph-001", "semantic_domains": ["logos.utterance.word", "logos.core", "communication.articulation", "divine.revelation", "existence.utterance"], "morphology_tags": ["noun", "masculine", "singular", "root:\u05d3\u05d1\u05e8", "triliteral:D-B-R"]}
|
||||
{"entry_id": "he-002", "surface": "\u05e8\u05d0\u05e9\u05d9\u05ea", "lemma": "\u05e8\u05d0\u05e9\u05d9\u05ea", "language": "he", "pos": "NOUN", "morphology_id": "he-morph-002", "semantic_domains": ["logos.genesis.origin", "logos.core", "origin", "existence.ground", "temporal.absolute"], "morphology_tags": ["noun", "feminine", "singular", "root:\u05e8\u05d0\u05e9", "construct_state"]}
|
||||
{"entry_id": "he-003", "surface": "\u05d0\u05d5\u05e8", "lemma": "\u05d0\u05d5\u05e8", "language": "he", "pos": "NOUN", "morphology_id": "he-morph-003", "semantic_domains": ["logos.illumination.photon", "logos.core", "logos.illumination", "divine.revelation", "knowledge.clarity"], "morphology_tags": ["noun", "masculine", "singular", "root:\u05d0\u05d5\u05e8", "triliteral:A-W-R"]}
|
||||
{"entry_id": "he-004", "surface": "\u05d7\u05d9\u05d9\u05dd", "lemma": "\u05d7\u05d9\u05d9\u05dd", "language": "he", "pos": "NOUN", "morphology_id": "he-morph-004", "semantic_domains": ["logos.vitality.animate", "logos.core", "vitality", "existence.manifestation", "being.animate"], "morphology_tags": ["noun", "masculine", "plural", "root:\u05d7\u05d9\u05d4", "triliteral:H-Y-H"]}
|
||||
{"entry_id": "he-005", "surface": "\u05d0\u05de\u05ea", "lemma": "\u05d0\u05de\u05ea", "language": "he", "pos": "NOUN", "morphology_id": "he-morph-005", "semantic_domains": ["logos.aletheia.verity", "logos.core", "epistemic.ground", "knowledge.ground", "divine.revelation"], "morphology_tags": ["noun", "feminine", "singular", "root:\u05d0\u05de\u05df", "triliteral:A-M-N"]}
|
||||
{"entry_id": "he-006", "surface": "\u05e8\u05d5\u05d7", "lemma": "\u05e8\u05d5\u05d7", "language": "he", "pos": "NOUN", "morphology_id": "he-morph-006", "semantic_domains": ["logos.pneuma.breath", "logos.core", "breath", "existence.animating", "logos.presence"], "morphology_tags": ["noun", "feminine", "singular", "root:\u05e8\u05d5\u05d7", "triliteral:R-W-H"]}
|
||||
{"entry_id": "he-007", "surface": "\u05d1\u05e8\u05d0", "lemma": "\u05d1\u05e8\u05d0", "language": "he", "pos": "VERB", "morphology_id": "he-morph-007", "semantic_domains": ["logos.ktizo.formation", "logos.core", "formation", "logos.genesis", "divine.agency"], "morphology_tags": ["verb", "qal", "perfect", "third_person", "masculine", "singular", "root:\u05d1\u05e8\u05d0", "triliteral:B-R-A"]}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
"normalization_policy": "unitize_versor",
|
||||
"source_manifest": "he_logos_micro_v1.lexicon.jsonl",
|
||||
"determinism_class": "D0",
|
||||
"checksum": "ea1ac85dbdbfe01bebe97356279ba76bdf6653e46a19f31e3e5c8050e33b601d",
|
||||
"checksum": "6e2f30175937c56cae276007d9865555beb5dd0759925660ab259c54a67d7b1d",
|
||||
"version": "1.0.0",
|
||||
"gate_engaged": true,
|
||||
"oov_policy": "fail_closed"
|
||||
|
|
|
|||
7
language_packs/data/he_logos_micro_v1/morphology.jsonl
Normal file
7
language_packs/data/he_logos_micro_v1/morphology.jsonl
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
{"morphology_id": "he-morph-001", "surface": "\u05d3\u05d1\u05e8", "lemma": "\u05d3\u05d1\u05e8", "language": "he", "root": "\u05d3-\u05d1-\u05e8", "prefix_chain": [], "stem": "\u05d3\u05d1\u05e8", "inflection": {"pos": "noun", "gender": "masculine", "number": "singular"}, "suffix_chain": []}
|
||||
{"morphology_id": "he-morph-002", "surface": "\u05e8\u05d0\u05e9\u05d9\u05ea", "lemma": "\u05e8\u05d0\u05e9\u05d9\u05ea", "language": "he", "root": "\u05e8-\u05d0-\u05e9", "prefix_chain": [], "stem": "\u05e8\u05d0\u05e9", "inflection": {"pos": "noun", "gender": "feminine", "number": "singular", "state": "construct"}, "suffix_chain": ["\u05d9\u05ea"]}
|
||||
{"morphology_id": "he-morph-003", "surface": "\u05d0\u05d5\u05e8", "lemma": "\u05d0\u05d5\u05e8", "language": "he", "root": "\u05d0-\u05d5-\u05e8", "prefix_chain": [], "stem": "\u05d0\u05d5\u05e8", "inflection": {"pos": "noun", "gender": "masculine", "number": "singular"}, "suffix_chain": []}
|
||||
{"morphology_id": "he-morph-004", "surface": "\u05d7\u05d9\u05d9\u05dd", "lemma": "\u05d7\u05d9\u05d9\u05dd", "language": "he", "root": "\u05d7-\u05d9-\u05d4", "prefix_chain": [], "stem": "\u05d7\u05d9", "inflection": {"pos": "noun", "gender": "masculine", "number": "plural"}, "suffix_chain": ["\u05d9\u05dd"]}
|
||||
{"morphology_id": "he-morph-005", "surface": "\u05d0\u05de\u05ea", "lemma": "\u05d0\u05de\u05ea", "language": "he", "root": "\u05d0-\u05de-\u05df", "prefix_chain": [], "stem": "\u05d0\u05de\u05ea", "inflection": {"pos": "noun", "gender": "feminine", "number": "singular"}, "suffix_chain": []}
|
||||
{"morphology_id": "he-morph-006", "surface": "\u05e8\u05d5\u05d7", "lemma": "\u05e8\u05d5\u05d7", "language": "he", "root": "\u05e8-\u05d5-\u05d7", "prefix_chain": [], "stem": "\u05e8\u05d5\u05d7", "inflection": {"pos": "noun", "gender": "feminine", "number": "singular"}, "suffix_chain": []}
|
||||
{"morphology_id": "he-morph-007", "surface": "\u05d1\u05e8\u05d0", "lemma": "\u05d1\u05e8\u05d0", "language": "he", "root": "\u05d1-\u05e8-\u05d0", "prefix_chain": [], "stem": "\u05d1\u05e8\u05d0", "inflection": {"pos": "verb", "binyan": "qal", "aspect": "perfect", "person": "third", "gender": "masculine", "number": "singular"}, "suffix_chain": []}
|
||||
|
|
@ -75,6 +75,7 @@ class MorphologyEntry:
|
|||
be preserved exactly.
|
||||
"""
|
||||
|
||||
morphology_id: str
|
||||
surface: str
|
||||
lemma: str
|
||||
language: str
|
||||
|
|
@ -84,6 +85,16 @@ class MorphologyEntry:
|
|||
inflection: Mapping[str, str] = field(default_factory=dict)
|
||||
suffix_chain: tuple[str, ...] = field(default_factory=tuple)
|
||||
|
||||
def __post_init__(self) -> None:
|
||||
if not self.morphology_id:
|
||||
raise ValueError("MorphologyEntry.morphology_id is required.")
|
||||
if not self.surface:
|
||||
raise ValueError("MorphologyEntry.surface is required.")
|
||||
if not self.lemma:
|
||||
raise ValueError("MorphologyEntry.lemma is required.")
|
||||
if not self.language:
|
||||
raise ValueError("MorphologyEntry.language is required.")
|
||||
|
||||
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class LexicalEntry:
|
||||
|
|
|
|||
5
morphology/__init__.py
Normal file
5
morphology/__init__.py
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
"""Morphology registry for language-pack root and inflection structure."""
|
||||
|
||||
from .registry import MorphologyRegistry, load_morphology
|
||||
|
||||
__all__ = ["MorphologyRegistry", "load_morphology"]
|
||||
86
morphology/registry.py
Normal file
86
morphology/registry.py
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
"""
|
||||
Morphology registry — reconstructive root and inflection structure.
|
||||
|
||||
The registry is deliberately pure schema + stdlib. It does not compile
|
||||
versors, import algebra, or participate in propagation. Its job is to make
|
||||
LexicalEntry.morphology_id resolve to ordered morphological structure.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
from language_packs.schema import MorphologyEntry
|
||||
|
||||
_DATA_DIR = Path(__file__).parent.parent / "language_packs" / "data"
|
||||
|
||||
|
||||
class MorphologyRegistry:
|
||||
"""Immutable morphology lookup table for one language pack."""
|
||||
|
||||
def __init__(self, entries: list[MorphologyEntry]) -> None:
|
||||
self._entries: tuple[MorphologyEntry, ...] = tuple(entries)
|
||||
self._by_id: dict[str, MorphologyEntry] = {}
|
||||
self._by_surface: dict[str, MorphologyEntry] = {}
|
||||
for entry in self._entries:
|
||||
if entry.morphology_id in self._by_id:
|
||||
raise ValueError(f"Duplicate morphology_id: {entry.morphology_id}")
|
||||
self._by_id[entry.morphology_id] = entry
|
||||
self._by_surface[entry.surface] = entry
|
||||
|
||||
def __len__(self) -> int:
|
||||
return len(self._entries)
|
||||
|
||||
@property
|
||||
def entries(self) -> tuple[MorphologyEntry, ...]:
|
||||
return self._entries
|
||||
|
||||
def get(self, morphology_id: str) -> MorphologyEntry | None:
|
||||
"""Return an entry by morphology_id, or None if absent."""
|
||||
return self._by_id.get(morphology_id)
|
||||
|
||||
def for_surface(self, surface: str) -> MorphologyEntry | None:
|
||||
"""Return an entry by surface form, or None if absent."""
|
||||
return self._by_surface.get(surface)
|
||||
|
||||
def require(self, morphology_id: str) -> MorphologyEntry:
|
||||
"""Return an entry by morphology_id, raising if the link is broken."""
|
||||
entry = self.get(morphology_id)
|
||||
if entry is None:
|
||||
raise KeyError(f"Morphology id '{morphology_id}' not in registry.")
|
||||
return entry
|
||||
|
||||
|
||||
def _parse_entry(payload: dict) -> MorphologyEntry:
|
||||
return MorphologyEntry(
|
||||
morphology_id=payload["morphology_id"],
|
||||
surface=payload["surface"],
|
||||
lemma=payload["lemma"],
|
||||
language=payload["language"],
|
||||
root=payload.get("root"),
|
||||
prefix_chain=tuple(payload.get("prefix_chain", [])),
|
||||
stem=payload.get("stem"),
|
||||
inflection=dict(payload.get("inflection", {})),
|
||||
suffix_chain=tuple(payload.get("suffix_chain", [])),
|
||||
)
|
||||
|
||||
|
||||
def load_morphology(pack_id: str) -> MorphologyRegistry:
|
||||
"""
|
||||
Load MorphologyEntry records from language_packs/data/<pack_id>/morphology.jsonl.
|
||||
|
||||
Packs without morphology data return an empty registry; packs that set
|
||||
LexicalEntry.morphology_id are validated by the compiler against this
|
||||
registry.
|
||||
"""
|
||||
morphology_path = _DATA_DIR / pack_id / "morphology.jsonl"
|
||||
if not morphology_path.exists():
|
||||
return MorphologyRegistry([])
|
||||
|
||||
entries: list[MorphologyEntry] = []
|
||||
for line in morphology_path.read_text(encoding="utf-8").splitlines():
|
||||
line = line.strip()
|
||||
if line:
|
||||
entries.append(_parse_entry(json.loads(line)))
|
||||
return MorphologyRegistry(entries)
|
||||
50
tests/test_morphology_registry.py
Normal file
50
tests/test_morphology_registry.py
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
from language_packs import load_pack_entries
|
||||
from morphology.registry import MorphologyRegistry, load_morphology
|
||||
|
||||
|
||||
def test_hebrew_morphology_registry_loads_triliteral_roots():
|
||||
registry = load_morphology("he_logos_micro_v1")
|
||||
|
||||
assert len(registry) == 7
|
||||
davar = registry.require("he-morph-001")
|
||||
assert davar.surface == "\u05d3\u05d1\u05e8"
|
||||
assert davar.root == "\u05d3-\u05d1-\u05e8"
|
||||
assert davar.inflection["gender"] == "masculine"
|
||||
|
||||
|
||||
def test_greek_morphology_registry_preserves_ordered_prefix_and_suffix():
|
||||
registry = load_morphology("grc_logos_micro_v1")
|
||||
|
||||
aletheia = registry.for_surface("\u1f00\u03bb\u03ae\u03b8\u03b5\u03b9\u03b1")
|
||||
assert aletheia is not None
|
||||
assert aletheia.prefix_chain == ("\u1f00",)
|
||||
assert aletheia.stem == "\u03bb\u03b7\u03b8\u03b5\u03b9"
|
||||
assert aletheia.suffix_chain == ("\u03b1",)
|
||||
assert aletheia.inflection["case"] == "nominative"
|
||||
|
||||
|
||||
def test_depth_pack_lexicon_entries_resolve_morphology_ids():
|
||||
registry = load_morphology("he_logos_micro_v1")
|
||||
entries = load_pack_entries("he_logos_micro_v1")
|
||||
|
||||
assert all(entry.morphology_id for entry in entries)
|
||||
for entry in entries:
|
||||
morph = registry.require(entry.morphology_id or "")
|
||||
assert morph.surface == entry.surface
|
||||
assert morph.lemma == entry.lemma
|
||||
|
||||
|
||||
def test_pack_without_morphology_returns_empty_registry():
|
||||
registry = load_morphology("en_minimal_v1")
|
||||
assert len(registry) == 0
|
||||
assert registry.get("missing") is None
|
||||
|
||||
|
||||
def test_morphology_registry_rejects_duplicate_ids():
|
||||
entry = load_morphology("he_logos_micro_v1").require("he-morph-001")
|
||||
with pytest.raises(ValueError, match="Duplicate morphology_id"):
|
||||
MorphologyRegistry([entry, entry])
|
||||
Loading…
Reference in a new issue