feat: Änderungsbericht Rohtext → Korrektur (aenderungen.py)
- Wortvergleich ohne Groß-/Kleinschreibung, Satzzeichen und „z. B.“-Varianten, damit nur echte Wortänderungen auffallen - HTML-Bericht: Änderungen nach Häufigkeit, korrigierter Text mit markierten Ersetzungen (Original dahinter), Einfügungen, Streichungen, Prüfmarken - rede_korrigieren.py schreibt den Bericht bei jedem Lauf (--kein-bericht) und zeigt die häufigsten Änderungen; Vergleich mit dem Rohtext vor Glossar - aenderungen.py als eigenes Skript für vorhandene Dateien Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
6610679d80
commit
c1d270bc0e
2 changed files with 397 additions and 1 deletions
99
tests/test_aenderungen.py
Normal file
99
tests/test_aenderungen.py
Normal file
|
|
@ -0,0 +1,99 @@
|
|||
"""Tests for aenderungen.py (change report raw transcript -> corrected text)."""
|
||||
import html
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
import aenderungen as ae
|
||||
|
||||
|
||||
def changes(raw, corrected):
|
||||
return [(tag, " ".join(old), " ".join(new))
|
||||
for tag, old, new in ae.compare(raw, corrected).changes()]
|
||||
|
||||
|
||||
def report_text(page):
|
||||
"""Corrected text as shown in the report, without original/deleted words."""
|
||||
body = page.split("<h2>Korrigierter Text</h2>")[1]
|
||||
body = re.sub(r"<del[^>]*>.*?</del>|<span class=orig>.*?</span>", "", body)
|
||||
return html.unescape(re.sub(r"<[^>]+>", " ", body)).split()
|
||||
|
||||
|
||||
@pytest.mark.parametrize("raw, corrected", [
|
||||
("das ist gut", "Das ist gut."), # case, punctuation
|
||||
("all das zerstritten", "All das – zerstritten."), # dash only
|
||||
("wie z.B. hier", "wie z. B. hier"), # split abbreviation
|
||||
("eins zwei", "eins\n\nzwei"), # paragraph break
|
||||
])
|
||||
def test_expected_edits_are_no_changes(raw, corrected):
|
||||
assert changes(raw, corrected) == []
|
||||
|
||||
|
||||
def test_replace_insert_delete():
|
||||
raw = "die gedeiliche Zusammenarbeit war das gut heute"
|
||||
corrected = "die gedeihliche Zusammenarbeit war doch gut"
|
||||
assert changes(raw, corrected) == [
|
||||
("replace", "gedeiliche", "gedeihliche"),
|
||||
("replace", "das", "doch"),
|
||||
("delete", "heute", ""),
|
||||
]
|
||||
assert changes("ein Wort", "ein neues Wort") == [("insert", "", "neues")]
|
||||
|
||||
|
||||
def test_change_summary_counts_and_sorts():
|
||||
raw = "Kremmel und Kremmel und Bar"
|
||||
corrected = "Kreml und Kreml und Bahr"
|
||||
assert ae.change_summary(ae.compare(raw, corrected)) == [
|
||||
("Kremmel", "Kreml", 2), ("Bar", "Bahr", 1),
|
||||
]
|
||||
|
||||
|
||||
def test_review_marks_are_not_changes_but_shown():
|
||||
corrected = "Er heißt Gerhard [prüfen: Ghard] Kocher."
|
||||
comparison = ae.compare("Er heißt Gerhard Kocher", corrected)
|
||||
assert list(comparison.changes()) == []
|
||||
page = ae.render_html(comparison, "t")
|
||||
assert "<span class=check>[prüfen: Ghard]</span>" in page
|
||||
assert "1 Prüfmarken" in page
|
||||
|
||||
|
||||
def test_render_html_shows_text_original_and_paragraphs():
|
||||
raw = "die gedeiliche Zusammenarbeit <b> ist gut heute neuer Absatz"
|
||||
corrected = "Die gedeihliche Zusammenarbeit <b> ist gut.\n\nNeuer Absatz."
|
||||
page = ae.render_html(ae.compare(raw, corrected), "Rede & Co")
|
||||
|
||||
assert report_text(page) == corrected.split()
|
||||
assert "title='Original: gedeiliche'" in page
|
||||
assert "<del title='gestrichen'>heute</del>" in page
|
||||
assert "<b>" in page and "<b>" not in page.split("<body>")[1]
|
||||
assert "Rede & Co" in page
|
||||
assert page.split("<h2>Korrigierter Text</h2>")[1].count("<p>") == 2
|
||||
|
||||
|
||||
def test_report_path():
|
||||
assert ae.report_path(Path("a/rede.korrigiert.txt")) == Path("a/rede.aenderungen.html")
|
||||
assert ae.report_path(Path("out.txt")) == Path("out.aenderungen.html")
|
||||
|
||||
|
||||
def test_cli(tmp_path, monkeypatch, capsys):
|
||||
raw = tmp_path / "rede.vtt"
|
||||
raw.write_text(
|
||||
"WEBVTT\n\n00:00:01.000 --> 00:00:02.000\ndas Kremmel heute\n", encoding="utf-8"
|
||||
)
|
||||
corrected = tmp_path / "rede.korrigiert.txt"
|
||||
corrected.write_text("Das Kreml heute.\n", encoding="utf-8")
|
||||
monkeypatch.setattr(sys, "argv", ["aenderungen.py", "-a", str(raw), "-b", str(corrected)])
|
||||
ae.main()
|
||||
|
||||
assert (tmp_path / "rede.aenderungen.html").is_file()
|
||||
out = capsys.readouterr().out
|
||||
assert "1× „Kremmel“ → „Kreml“" in out
|
||||
|
||||
|
||||
def test_cli_missing_file(tmp_path, monkeypatch, capsys):
|
||||
monkeypatch.setattr(sys, "argv", ["aenderungen.py", "-a", "fehlt.txt", "-b", "fehlt.txt"])
|
||||
with pytest.raises(SystemExit):
|
||||
ae.main()
|
||||
assert "Datei nicht gefunden" in capsys.readouterr().err
|
||||
|
|
@ -319,6 +319,28 @@ def test_main_vtt_input_matches_txt_input(sample_copy, monkeypatch):
|
|||
) == expected.read_text(encoding="utf-8")
|
||||
|
||||
|
||||
def test_main_writes_change_report(sample_copy, monkeypatch, capsys):
|
||||
def correct(chunk, index, system):
|
||||
return chunk.strip().replace("Rammstein", "Ramstein")
|
||||
|
||||
monkeypatch.setattr(rk, "correct_chunk", correct)
|
||||
monkeypatch.setattr(rk, "find_paragraph_starts", every_nth_start(5))
|
||||
run_main(monkeypatch, "-i", sample_copy)
|
||||
|
||||
report = sample_copy.with_name("ramstein.de.aenderungen.html")
|
||||
assert "title='Original: Rammstein" in report.read_text(encoding="utf-8")
|
||||
out = capsys.readouterr().out
|
||||
assert "„Rammstein“ → „Ramstein“" in out
|
||||
assert f"Bericht: {report}" in out
|
||||
|
||||
|
||||
def test_main_without_change_report(sample_copy, monkeypatch):
|
||||
monkeypatch.setattr(rk, "correct_chunk", identity_correct)
|
||||
monkeypatch.setattr(rk, "find_paragraph_starts", every_nth_start(5))
|
||||
run_main(monkeypatch, "-i", sample_copy, "--kein-bericht")
|
||||
assert not sample_copy.with_name("ramstein.de.aenderungen.html").exists()
|
||||
|
||||
|
||||
def test_main_model_option(sample_copy, monkeypatch):
|
||||
default = rk.MODEL
|
||||
used = []
|
||||
|
|
|
|||
Loading…
Reference in a new issue