feat: Abkürzungen und Ordnungszahlen beenden keinen Satz
„z. B.“, „d. h.“, „Dr.“, „bzw.“, „ca.“, „e.g.“ und Ordnungszahlen wie „2. Weltkrieg“ oder „am 24.“ gelten bei Satz-, Abschnitts- und Absatzgrenzen nicht mehr als Satzende; „usw.“, „etc.“ und Jahreszahlen weiterhin. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
82ce30ca72
commit
f1d2e201df
3 changed files with 46 additions and 6 deletions
10
README.md
10
README.md
|
|
@ -250,8 +250,9 @@ Blöcken korrigiert.
|
||||||
2. **Feste Ersetzungen** aus dem Glossar (`falsch => richtig`) werden im Rohtext
|
2. **Feste Ersetzungen** aus dem Glossar (`falsch => richtig`) werden im Rohtext
|
||||||
angewendet, bevor das Modell ihn sieht.
|
angewendet, bevor das Modell ihn sieht.
|
||||||
3. **Korrektur:** Der Text wird in Abschnitte von ca. 1200 Wörtern geteilt (immer
|
3. **Korrektur:** Der Text wird in Abschnitte von ca. 1200 Wörtern geteilt (immer
|
||||||
am Satzende) und abschnittsweise vom Modell korrigiert. Fortschritt wird live
|
am Satzende; Abkürzungen wie „z. B.“, „Dr.“, „bzw.“ und Ordnungszahlen wie
|
||||||
angezeigt.
|
„2. Weltkrieg“ zählen nicht als Satzende) und abschnittsweise vom Modell
|
||||||
|
korrigiert. Fortschritt wird live angezeigt.
|
||||||
4. **Wortzahl-Prüfung:** Weicht die Wortzahl eines Abschnitts um mehr als 5 % ab,
|
4. **Wortzahl-Prüfung:** Weicht die Wortzahl eines Abschnitts um mehr als 5 % ab,
|
||||||
gibt es eine Warnung (mögliche Auslassungen oder Ergänzungen).
|
gibt es eine Warnung (mögliche Auslassungen oder Ergänzungen).
|
||||||
5. **Absätze:** Das Modell nennt nur Satznummern, an denen ein Absatz beginnt; der
|
5. **Absätze:** Das Modell nennt nur Satznummern, an denen ein Absatz beginnt; der
|
||||||
|
|
@ -457,5 +458,6 @@ Oben in `abschrift_korrigieren.py`:
|
||||||
- `vtt2txt.py` erkennt Wiederholungen an der Überlappung aufeinanderfolgender
|
- `vtt2txt.py` erkennt Wiederholungen an der Überlappung aufeinanderfolgender
|
||||||
Untertitel-Blöcke. Bei Untertiteln ohne rollendes Fenster gehen deshalb zwei
|
Untertitel-Blöcke. Bei Untertiteln ohne rollendes Fenster gehen deshalb zwei
|
||||||
direkt aufeinanderfolgende, identische Blöcke zu einem zusammen.
|
direkt aufeinanderfolgende, identische Blöcke zu einem zusammen.
|
||||||
- Abkürzungen wie „z. B.“ oder „2. Weltkrieg“ können als Satzende gelten; das
|
- Einzelbuchstaben und ein- bis zweistellige Zahlen mit Punkt gelten nie als
|
||||||
beeinflusst nur mögliche Absatzgrenzen, nicht den Text.
|
Satzende („Plan B.“, „Es waren 12.“); das beeinflusst nur mögliche Absatzgrenzen,
|
||||||
|
nicht den Text.
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,11 @@ MIN_PARAGRAPH_SENTENCES = 3 # kürzere Absätze werden mit dem Nachbarn verbun
|
||||||
|
|
||||||
SENTENCE_END = re.compile(r"[.!?…][\"'»«“”)\]]*$")
|
SENTENCE_END = re.compile(r"[.!?…][\"'»«“”)\]]*$")
|
||||||
SENTENCE_SPLIT = re.compile(r"(?:(?<=[.!?…])|(?<=[.!?…][\"'»«“”)\]]))\s+")
|
SENTENCE_SPLIT = re.compile(r"(?:(?<=[.!?…])|(?<=[.!?…][\"'»«“”)\]]))\s+")
|
||||||
|
# Abbreviations and ordinals ("z. B.", "Dr.", "2. Weltkrieg", "e.g.") end no
|
||||||
|
# sentence; "usw." and "etc." usually do, four-digit years too
|
||||||
|
NO_SENTENCE_END = re.compile(
|
||||||
|
r"(?:^|[\s.])(?:\w|\d{1,2}|Dr|Prof|bzw|ca|Nr|St|vgl|sog|evtl|ggf|inkl|bspw|Mr|Mrs|Ms)\.$"
|
||||||
|
)
|
||||||
|
|
||||||
SYSTEM = """Du korrigierst die automatische Abschrift eines deutschsprachigen
|
SYSTEM = """Du korrigierst die automatische Abschrift eines deutschsprachigen
|
||||||
gesprochenen Beitrags (z. B. Rede, Vortrag, Interview, Video).
|
gesprochenen Beitrags (z. B. Rede, Vortrag, Interview, Video).
|
||||||
|
|
@ -237,7 +242,7 @@ def split_chunks(text):
|
||||||
while start < len(units):
|
while start < len(units):
|
||||||
end = min(start + CHUNK_SIZE, len(units))
|
end = min(start + CHUNK_SIZE, len(units))
|
||||||
limit = min(end + MAX_EXTRA_WORDS, len(units))
|
limit = min(end + MAX_EXTRA_WORDS, len(units))
|
||||||
while end < limit and not SENTENCE_END.search(units[end - 1].rstrip()):
|
while end < limit and not ends_sentence(units[end - 1].rstrip()):
|
||||||
end += 1
|
end += 1
|
||||||
chunks.append("".join(units[start:end]))
|
chunks.append("".join(units[start:end]))
|
||||||
start = end
|
start = end
|
||||||
|
|
@ -349,9 +354,21 @@ def correct_chunk(chunk, index, system):
|
||||||
return corrected
|
return corrected
|
||||||
|
|
||||||
|
|
||||||
|
def ends_sentence(text):
|
||||||
|
return bool(SENTENCE_END.search(text)) and not NO_SENTENCE_END.search(text)
|
||||||
|
|
||||||
|
|
||||||
def split_sentences(text):
|
def split_sentences(text):
|
||||||
"""Split flat text into sentences; joining them with spaces restores it."""
|
"""Split flat text into sentences; joining them with spaces restores it."""
|
||||||
return [s for s in SENTENCE_SPLIT.split(" ".join(text.split())) if s]
|
sentences = []
|
||||||
|
for piece in SENTENCE_SPLIT.split(" ".join(text.split())):
|
||||||
|
if not piece:
|
||||||
|
continue
|
||||||
|
if sentences and NO_SENTENCE_END.search(sentences[-1]):
|
||||||
|
sentences[-1] += " " + piece
|
||||||
|
else:
|
||||||
|
sentences.append(piece)
|
||||||
|
return sentences
|
||||||
|
|
||||||
|
|
||||||
def find_paragraph_starts(sentences, first, system):
|
def find_paragraph_starts(sentences, first, system):
|
||||||
|
|
|
||||||
|
|
@ -207,6 +207,27 @@ def test_split_sentences_keeps_closing_quotes():
|
||||||
assert " ".join(sentences) == text
|
assert " ".join(sentences) == text
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("text, expected", [
|
||||||
|
("Das gilt z. B. hier. Gut.", ["Das gilt z. B. hier.", "Gut."]),
|
||||||
|
("Das heißt, d. h. nicht. Gut.", ["Das heißt, d. h. nicht.", "Gut."]),
|
||||||
|
("Dr. Müller und Prof. Meier kamen. Gut.", ["Dr. Müller und Prof. Meier kamen.", "Gut."]),
|
||||||
|
("Im 2. Weltkrieg am 3. Oktober. Gut.", ["Im 2. Weltkrieg am 3. Oktober.", "Gut."]),
|
||||||
|
("Das war 1982. Dann kam 1983.", ["Das war 1982.", "Dann kam 1983."]),
|
||||||
|
("Fruit, e.g. apples. And Mr. Smith.", ["Fruit, e.g. apples.", "And Mr. Smith."]),
|
||||||
|
("Äpfel, Birnen usw. Dann mehr.", ["Äpfel, Birnen usw.", "Dann mehr."]),
|
||||||
|
])
|
||||||
|
def test_split_sentences_abbreviations(text, expected):
|
||||||
|
sentences = ak.split_sentences(text)
|
||||||
|
assert sentences == expected
|
||||||
|
assert " ".join(sentences) == text
|
||||||
|
|
||||||
|
|
||||||
|
def test_split_chunks_does_not_end_at_abbreviation(monkeypatch):
|
||||||
|
monkeypatch.setattr(ak, "CHUNK_SIZE", 3)
|
||||||
|
chunks = ak.split_chunks("Eins zwei z. B. drei. Vier fünf sechs.")
|
||||||
|
assert chunks == ["Eins zwei z. B. drei. ", "Vier fünf sechs."]
|
||||||
|
|
||||||
|
|
||||||
def test_group_paragraphs():
|
def test_group_paragraphs():
|
||||||
assert ak.group_paragraphs(7, {3, 6}) == [(1, 2), (3, 5), (6, 7)]
|
assert ak.group_paragraphs(7, {3, 6}) == [(1, 2), (3, 5), (6, 7)]
|
||||||
assert ak.group_paragraphs(3, set()) == [(1, 3)]
|
assert ak.group_paragraphs(3, set()) == [(1, 3)]
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue