textmachine/eval/bank_arbitration/value_demo.py

85 lines
3.9 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python3
"""$0-демо ценности рекомендаций §D корзины 1 на ЖИВОМ банке coldrun-a (вопрос владельца 04.08
«насколько алгоритмы лучше текущего»):
A4: intra-run конфликты consolidated×consolidated (containment + лексемный subset — реплика
CanonConflicts/lexemeSubset из terminology.go) — сколько противоречий сегодня НЕ видит никто;
A5: сдвиг ЧАСТОТНОГО топа вариантов при фолде голосов по нормализованной форме.
"""
import unicodedata
from pathlib import Path
import sys
sys.path.insert(0, str(Path(__file__).resolve().parent.parent / "bank_autonomy"))
from parse_common import parse_bankstop # noqa: E402
STEM_MIN, SLACK = 3, 2 # stemMinPrefix / stemSuffixSlack (terminology.go:379-386)
def norm(s):
s = unicodedata.normalize("NFKC", s or "").casefold().replace("ё", "е")
for ch in "«»\"'":
s = s.replace(ch, "")
return " ".join(s.split())
def words(s):
return [w for w in "".join(c if c.isalnum() else " " for c in norm(s)).split() if len(w) > 1]
def same_lexeme(a, b):
if a == b:
return True
n = 0
while n < len(a) and n < len(b) and a[n] == b[n]:
n += 1
shorter = min(len(a), len(b))
if (n == len(a) or n == len(b)) and shorter >= STEM_MIN:
return True
return n >= max(shorter - SLACK, STEM_MIN)
def lexeme_subset(need, have):
return all(any(same_lexeme(w, h) for h in have) for w in need) if need else True
def a4_conflicts():
bs = parse_bankstop()
finals = {s: r["dst"] for s, r in bs.items() if r.get("dst") and r["dst"] != ""}
out = []
for a, da in finals.items():
for b, db in finals.items():
if a == b or b not in a: # b строго содержится в a (containment, как CanonConflicts)
continue
if not lexeme_subset(words(db), words(da)):
out.append((a, da, b, db))
print(f"A4-демо: финалов с dst {len(finals)}; НЕВИДИМЫХ сегодня внутри-прогонных противоречий "
f"(составной терм теряет перевод вложенного): {len(out)}")
for a, da, b, db in sorted(out):
print(f" {a}{da!r} не несёт {b}{db!r}")
def a5_topshift():
bs = parse_bankstop()
shifted = []
for s, r in bs.items():
vs = r.get("drafts") or []
if len(vs) < 2:
continue
raw_top, raw_top_n = vs[0][0], vs[0][1] # порядок §C2-3 (частота — главный фактор при пустом каноне)
folded = {}
for d, n in vs:
folded[norm(d)] = folded.get(norm(d), 0) + n
fold_top = max(folded, key=lambda k: (folded[k], k))
# ЧЕСТНЫЙ сдвиг: слитые голоса ОБОГНАЛИ сырой топ (строго больше). Равенство после фолда —
# тай-брейк, не эффект нормализации (ловушка первой версии этого скрипта — поймана самопроверкой).
if norm(raw_top) != fold_top and folded[fold_top] > raw_top_n:
shifted.append((s, raw_top, fold_top, vs))
print(f"\nA5-демо: строк со spread>=2: {sum(1 for r in bs.values() if len(r.get('drafts') or [])>=2)}; "
f"частотный топ ЧЕСТНО сдвигается нормализованным фолдом (слитые голоса обогнали сырой топ) у: {len(shifted)}")
for s, rt, ft, vs in sorted(shifted):
print(f" {s}: сырой топ {rt!r} → фолднутый {ft!r} ({' | '.join(f'{d}×{n}' for d,n in vs[:5])})")
if __name__ == "__main__":
a4_conflicts()
a5_topshift()