feat: 0.2.0 – Konsens Qwen = Gemma = GLM als Standard, Gehrke komplett verarbeitet
- Dritte Lesart (GLM-4.6V über OpenRouter, ohne Nachdenken) muss zustimmen;
keine 2:1-Annahme; fehlender Schlusspunkt der dritten Lesart zählt nicht
- run in Paketen zu 50 mit Statuszeile, beide lokalen Modelle parallel,
GLM nur für Einige; nur die übernommene Lesart wird gegen das Bild geprüft
- Bild-Veto: Ähnlichkeit >= 0,65, Schriftgrößen-Prüfung abgeschaltet
- Antworten auf 800 Tokens begrenzt; Prompt-Version und Nachdenk-Einstellung
im Cache-Schlüssel; Verfahrensversion für Prüfergebnisse
- Behoben: Gleichungsnummern aus alten Lesarten ("(3.7)"), gleichzeitiges
Speichern zweier Engines, Absturz bei Formeln ohne aktuelle Lesart
- apply: deutsches Dezimalkomma 3{,}6; Review-Galerie zeigt aktuelle Lesarten
- tools/vergleich.py auf Konsens-Ablauf umgestellt
- Doku: README, BEDIENUNGSANLEITUNG, neues CHANGELOG
Gehrke-Brückenkurs: 942 von 1505 Formelbildern automatisch ersetzt (63 %),
62 von 62 von Hand geprüften korrekt; Pandoc/MathML 945 von 945.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
fe95cb572c
commit
388024a17b
1 changed files with 586 additions and 277 deletions
|
|
@ -131,22 +131,33 @@ def test_consensus_mode(epub2md_dir: Path) -> None:
|
|||
ws.save(display)
|
||||
pipeline.verify_all(source, ws, preset="cm", workers=2, quiet=True)
|
||||
st = {f.asset.rsplit("/", 1)[1]: f for f in ws.all()}
|
||||
assert st["0001-inline.jpg"].status == "auto_accepted", st["0001-inline.jpg"].reason
|
||||
assert pipeline.needs_third(st["0001-inline.jpg"]), st["0001-inline.jpg"].reason # A = B, C missing
|
||||
assert st["0002-display.jpg"].status == "needs_review"
|
||||
assert "uneinig" in st["0002-display.jpg"].reason
|
||||
|
||||
# a third reading breaks the tie
|
||||
d = st["0002-display.jpg"]
|
||||
# the third reading confirms A = B -> accepted; for A != B a third reading does not decide 2:1
|
||||
i, d = st["0001-inline.jpg"], st["0002-display.jpg"]
|
||||
i.candidates.append(_reading("cloud", "C", r"L=\left\{-\sqrt{2};\sqrt{2}\right\}"))
|
||||
d.candidates.append(_reading("cloud", "C", r"x_{2,k} = \arcsin(-\frac35) + 2k\pi"))
|
||||
ws.save(i)
|
||||
ws.save(d)
|
||||
pipeline.verify_all(source, ws, preset="cm", workers=2, quiet=True)
|
||||
d = ws.load(d.id)
|
||||
assert d.status == "auto_accepted" and "Stichwahl" in d.reason, d.reason
|
||||
i, d = ws.load(i.id), ws.load(d.id)
|
||||
assert i.status == "auto_accepted" and "dritte Lesart bestätigt" in i.reason, i.reason
|
||||
assert d.status == "needs_review"
|
||||
|
||||
# a disagreeing third reading blocks the acceptance
|
||||
i.candidates[-1] = _reading("cloud", "C", r"L=\{-\sqrt{2};\sqrt{3}\}")
|
||||
i.status = "pending"
|
||||
ws.save(i)
|
||||
pipeline.verify_all(source, ws, preset="cm", workers=2, quiet=True)
|
||||
i = ws.load(i.id)
|
||||
assert i.status == "needs_review" and "weicht ab" in i.reason, i.reason
|
||||
|
||||
|
||||
def test_consensus_rules() -> None:
|
||||
from formula_img2latex.decide import consensus_thresholds, decide_consensus
|
||||
th = consensus_thresholds()
|
||||
th = consensus_thresholds({"min_p_mean": 0.9, "min_p_min": 0.3, "require_third": 0})
|
||||
a, b = _reading("local", "A", "x^2"), _reading("local2", "B", "x^{2}")
|
||||
veto = {"ok": True, "metrics": {"ncc": 0.9, "px_per_pt": 1.4}}
|
||||
assert decide_consensus([a, b], [(0, 1, "normalform")], veto, True, 1.4, th)[0] == "auto_accepted"
|
||||
|
|
@ -156,11 +167,18 @@ def test_consensus_rules() -> None:
|
|||
bad = {"ok": True, "metrics": {"ncc": 0.5, "px_per_pt": 1.4}}
|
||||
assert decide_consensus([a, b], [(0, 1, "normalform")], bad, True, 1.4, th)[0] == "needs_review" # veto
|
||||
big = {"ok": True, "metrics": {"ncc": 0.9, "px_per_pt": 1.8}}
|
||||
assert decide_consensus([a, b], [(0, 1, "normalform")], big, True, 1.4, th)[0] == "needs_review" # size
|
||||
sized = {**th, "veto_scale_dev": 0.15}
|
||||
assert decide_consensus([a, b], [(0, 1, "normalform")], big, True, 1.4, sized)[0] == "needs_review" # size check on
|
||||
assert decide_consensus([a, b], [(0, 1, "normalform")], veto, False, 1.4, th)[0] == "needs_review" # pandoc
|
||||
nf = [_reading("local", "A", None, is_formula=False), _reading("local2", "B", None, is_formula=False)]
|
||||
assert decide_consensus(nf, [], None, True, 1.4, th)[0] == "not_formula"
|
||||
assert decide_consensus([a], [], veto, True, 1.4, th)[0] == "needs_review" # one model
|
||||
strict = consensus_thresholds() # default: third must agree
|
||||
c = _reading("cloud", "C", "x^2")
|
||||
assert decide_consensus([a, b], [(0, 1, "normalform")], veto, True, 1.4, strict)[0] == "needs_review"
|
||||
assert decide_consensus([a, b, c], [(0, 1, "normalform"), (0, 2, "normalform"), (1, 2, "normalform")],
|
||||
veto, True, 1.4, strict)[0] == "auto_accepted"
|
||||
assert decide_consensus([a, b, c], [(0, 2, "normalform")], veto, True, 1.4, strict)[0] == "needs_review" # no 2:1
|
||||
|
||||
|
||||
def test_canonical_form() -> None:
|
||||
|
|
@ -185,3 +203,34 @@ def test_logprob_confidence() -> None:
|
|||
p_min, p_mean = latex_token_probs(content, items)
|
||||
assert p_min == 0.5 and 0.8 < p_mean < 0.9 # only the four LaTeX tokens count
|
||||
assert parse_confidence(content) == 97
|
||||
|
||||
|
||||
def test_equation_number_only_from_chosen_reading() -> None:
|
||||
from formula_img2latex.store import Formula
|
||||
f = Formula(id="f-x-d", sha256="x", asset="a.png", display=True)
|
||||
f.candidates = [{"engine": "local", "model": "old", "tex": r"a=b \tag{3.7}"}] # an old, hallucinated number
|
||||
assert pipeline._with_tag(f, "a=b") == "a=b"
|
||||
assert pipeline._with_tag(f, r"a=b \tag{2.1}") == r"a=b\qquad\text{(2.1)}"
|
||||
f.display = False
|
||||
assert pipeline._with_tag(f, r"a=b \tag{2.1}") == "a=b"
|
||||
|
||||
|
||||
def test_german_decimal_comma(epub2md_dir: Path) -> None:
|
||||
import json
|
||||
source, _ws = pipeline.open_all(epub2md_dir, None, create=True)
|
||||
assert source.format_math("3,6 + (1,2)", False) == "$3,6 + (1,2)$" # no language: unchanged
|
||||
rep = json.loads((epub2md_dir / "conversion-report.json").read_text())
|
||||
rep["metadata"] = {"language": "de"}
|
||||
(epub2md_dir / "conversion-report.json").write_text(json.dumps(rep))
|
||||
source, _ws = pipeline.open_all(epub2md_dir, None)
|
||||
assert source.format_math(r"\frac{72}{3,6} = 20, x", True) == r"$$\frac{72}{3{,}6} = 20, x$$"
|
||||
|
||||
|
||||
def test_verify_skips_formulas_without_current_reading(epub2md_dir: Path) -> None:
|
||||
source, ws = pipeline.open_all(epub2md_dir, None, create=True)
|
||||
pipeline.scan(source, ws, quiet=True)
|
||||
f = next(f for f in ws.all() if f.asset.endswith("0001-inline.jpg"))
|
||||
f.candidates = [{"engine": "local", "model": "A", "tex": "x", "is_formula": True, "prompt": 1}, # old reading
|
||||
{"engine": "local", "model": "A", "tex": None, "is_formula": False, "prompt": 2}] # current: none
|
||||
ws.save(f)
|
||||
pipeline.verify_all(source, ws, preset="cm", workers=1, quiet=True) # must not crash
|
||||
|
|
|
|||
Loading…
Reference in a new issue