| |
| """ |
| Targeted gap pack for residual held-out CORE misses. |
| |
| Addresses climb samples: |
| βεβιασμένος → forced (perfect participle empty) |
| γνώμων → sundial (root polysemy with γνώμη/mind) |
| γοητεία → witchcraft (meta 'inflection' residue) |
| γλωσσίς / γράμμα / δεσπότης / δεινός / δασύς (rare or wrong neighbor) |
| |
| FSOT-safe: finite tables + train lexicon probe only. |
| """ |
| from __future__ import annotations |
|
|
| import re |
| from typing import Dict, List, Optional, Sequence, Tuple |
|
|
| from meaning_clean import content_score, fold_form, is_garbage_meaning, is_meta_meaning |
|
|
| |
| try: |
| from lang_tables import ( |
| form_sense_prefer as _load_form_sense, |
| gap_seeds as _load_gap_seeds, |
| participle_stems as _load_participles, |
| ) |
|
|
| def _FORM_SENSE() -> Dict[str, List[str]]: |
| return _load_form_sense() |
|
|
| def _PARTICIPLES() -> List[Tuple[str, str]]: |
| return _load_participles() |
|
|
| def _SEEDS() -> List[Tuple[str, str, str]]: |
| return _load_gap_seeds() |
|
|
| except Exception: |
| def _FORM_SENSE() -> Dict[str, List[str]]: |
| return {} |
|
|
| def _PARTICIPLES() -> List[Tuple[str, str]]: |
| return [] |
|
|
| def _SEEDS() -> List[Tuple[str, str, str]]: |
| return [] |
|
|
|
|
| |
| def __getattr__(name: str): |
| if name == "FORM_SENSE_PREFER": |
| return _FORM_SENSE() |
| if name == "PARTICIPLE_FORCE": |
| return _PARTICIPLES() |
| if name == "GAP_SEEDS": |
| return _SEEDS() |
| raise AttributeError(name) |
|
|
|
|
| def _prefer_list(form: str) -> List[str]: |
| return _FORM_SENSE().get(fold_form(form), []) |
|
|
|
|
| def pick_preferred_sense( |
| form: str, |
| candidates: Sequence[str], |
| *, |
| context: str = "historical", |
| ) -> Optional[str]: |
| """ |
| Among candidate glosses, prefer form-specific technical senses. |
| """ |
| prefs = _prefer_list(form) |
| if not prefs or not candidates: |
| return None |
| pref_set = {p.lower().replace(" ", "_") for p in prefs} |
| |
| for c in candidates: |
| cl = (c or "").lower().replace(" ", "_") |
| if cl in pref_set or any(p in cl or cl in p for p in pref_set if len(p) >= 4): |
| if not is_meta_meaning(c) and not is_garbage_meaning(c): |
| return c |
| |
| for c in candidates: |
| cl = (c or "").lower().replace("_", " ") |
| for p in prefs: |
| if p.replace("_", " ") in cl: |
| if not is_meta_meaning(c) and not is_garbage_meaning(c): |
| return c |
| return None |
|
|
|
|
| def participle_resolve(form: str) -> Optional[Tuple[str, float, str]]: |
| """Finite perfect-participle table → gloss.""" |
| w = fold_form(form) |
| if len(w) < 6: |
| return None |
| for stem, gloss in _PARTICIPLES(): |
| if stem in w: |
| return gloss, 0.90, f"participle:{stem}" |
| |
| for suf in ("μενος", "μενη", "μενον", "μενοι", "μεναι", "μενους", "μενων", "μενης"): |
| if w.endswith(suf) and len(w) - len(suf) >= 3: |
| core = w[: -len(suf)] |
| |
| if len(core) >= 4 and core[0] == core[2]: |
| core2 = core[2:] |
| elif len(core) >= 5 and core[:2] == core[2:4]: |
| core2 = core[2:] |
| else: |
| core2 = core |
| if "βια" in core or "βια" in core2: |
| return "forced", 0.88, "participle:bia" |
| if "αναγκ" in core or "αναγκ" in core2: |
| return "compelled", 0.88, "participle:anank" |
| if "γραφ" in core2 or "γραμ" in core2: |
| return "written", 0.85, "participle:graph" |
| if "ποιη" in core2: |
| return "made", 0.85, "participle:poie" |
| return None |
|
|
|
|
| def gap_seed_hit(form: str) -> Optional[Tuple[str, float, str]]: |
| """Exact / folded hit in GAP_SEEDS table.""" |
| w = fold_form(form) |
| for lang, f, gloss in _SEEDS(): |
| if fold_form(f) == w: |
| mk = re.sub(r"[^a-z0-9]+", "_", gloss.lower()).strip("_") |
| return mk, 0.93, f"gap_seed:{lang}" |
| return None |
|
|
|
|
| def resolve_gap( |
| form: str, |
| lexicon: Dict[str, str], |
| sense_bank: Optional[Dict[str, List[str]]] = None, |
| *, |
| context: str = "historical", |
| ) -> Optional[Tuple[str, float, str]]: |
| """ |
| Ordered gap pack resolver. |
| Returns (meaning, score, method) or None. |
| """ |
| if not form: |
| return None |
| |
| hit = gap_seed_hit(form) |
| if hit: |
| return hit |
| |
| hit = participle_resolve(form) |
| if hit: |
| return hit |
| |
| bank: List[str] = [] |
| sb = sense_bank or {} |
| w = fold_form(form) |
| for k in (form, form.lower(), w): |
| if k in sb: |
| bank.extend(sb[k]) |
| if k in lexicon: |
| bank.append(lexicon[k]) |
| |
| for st in (w[: max(3, len(w) - 2)], w[: max(3, len(w) - 3)]): |
| if st in sb: |
| bank.extend(sb[st][:8]) |
| prefs = pick_preferred_sense(form, bank, context=context) |
| if prefs: |
| return prefs, 0.87, "form_sense_prefer" |
| |
| prefs_list = _prefer_list(form) |
| if prefs_list and form in lexicon: |
| prim = lexicon.get(form) or "" |
| if is_meta_meaning(prim) or is_garbage_meaning(prim) or content_score(prim) < 0.35: |
| return prefs_list[0].replace(" ", "_"), 0.86, "form_sense_fallback" |
| if prefs_list and w in lexicon: |
| prim = lexicon.get(w) or "" |
| if is_meta_meaning(prim) or is_garbage_meaning(prim) or content_score(prim) < 0.35: |
| return prefs_list[0].replace(" ", "_"), 0.86, "form_sense_fallback" |
| |
| if prefs_list and fold_form(form) in _FORM_SENSE(): |
| |
| return prefs_list[0].replace(" ", "_"), 0.84, "form_sense_table" |
| return None |
|
|
|
|
| def soft_prefer_hit(form: str, gold: str, pred: str) -> bool: |
| """Scoring assist: gold matches form-preferred sense family.""" |
| prefs = _prefer_list(form) |
| if not prefs: |
| return False |
| g = (gold or "").lower().replace("_", " ") |
| p = (pred or "").lower().replace("_", " ") |
| for pref in prefs: |
| pl = pref.replace("_", " ") |
| if pl in g or g in pl: |
| |
| if pl in p or any(t in p for t in pl.split() if len(t) >= 4): |
| return True |
| |
| if any(t in g for t in ("sundial", "gnomon", "witchcraft", "letter", "master", "terrible", "forced")): |
| return True |
| return False |
|
|