Files
talkscore-asr/tests/test_text.py
T
Vladimir BryzgalovandClaude Opus 5 d4a24af6f0 Готовые предложения в ответе для разметки ролей
Разбивка на предложения нужна LLM-разметке и была бы дублирована на стороне
Talkscore. Логика живёт рядом с ASR, который и ставит пунктуацию, а сам
вызов LLM остаётся на Talkscore: промпт меняется чаще, чем расшифровка,
и переразметить сохранённый текст дешевле, чем гонять аудио заново.

Документ интеграции дополнен итогами сравнения моделей, batch-ценами
и проверками формата ответа.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-16 18:38:34 +05:00

124 lines
6.3 KiB
Python
Raw 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.
"""Тесты постобработки текста: словарь замен и типографика."""
import pytest
from app.text import apply_replacements, load_replacements, normalize_typography, split_sentences
class TestReplacements:
def test_replaces_known_term(self):
rules = {"тэг менеджер": "Tag Manager"}
assert apply_replacements("открываем тэг менеджер", rules) == "открываем Tag Manager"
def test_replacement_is_case_insensitive_on_match(self):
rules = {"тэг менеджер": "Tag Manager"}
assert apply_replacements("Открываем Тэг Менеджер тут", rules) == "Открываем Tag Manager тут"
def test_replacement_keeps_surrounding_punctuation(self):
rules = {"джетур": "G-Tour"}
assert apply_replacements("бренд джетур, смотрите", rules) == "бренд G-Tour, смотрите"
def test_does_not_replace_inside_word(self):
"""«лид» не должен ломать «лидер»."""
rules = {"лед": "лид"}
assert apply_replacements("лидер рынка", rules) == "лидер рынка"
def test_multiword_replacement(self):
rules = {"гугл так менеджер": "Google Tag Manager"}
assert apply_replacements("это гугл так менеджер", rules) == "это Google Tag Manager"
def test_empty_rules_returns_original(self):
assert apply_replacements("текст без изменений", {}) == "текст без изменений"
def test_longest_rule_wins(self):
"""Более длинное правило применяется раньше короткого."""
rules = {"так менеджер": "Tag Manager", "гугл так менеджер": "Google Tag Manager"}
assert apply_replacements("гугл так менеджер", rules) == "Google Tag Manager"
class TestLoadReplacements:
def test_parses_simple_file(self, tmp_path):
f = tmp_path / "r.txt"
f.write_text("джетур = G-Tour\nтэг менеджер = Tag Manager\n", encoding="utf-8")
rules = load_replacements(f)
assert rules == {"джетур": "G-Tour", "тэг менеджер": "Tag Manager"}
def test_skips_comments_and_blanks(self, tmp_path):
f = tmp_path / "r.txt"
f.write_text("# комментарий\n\nджетур = G-Tour\n\n# ещё\n", encoding="utf-8")
assert load_replacements(f) == {"джетур": "G-Tour"}
def test_missing_file_returns_empty(self, tmp_path):
assert load_replacements(tmp_path / "нет.txt") == {}
def test_ignores_line_without_separator(self, tmp_path):
f = tmp_path / "r.txt"
f.write_text("мусор без разделителя\nджетур = G-Tour\n", encoding="utf-8")
assert load_replacements(f) == {"джетур": "G-Tour"}
class TestTypography:
@pytest.mark.parametrize("dash", ["—", "–", "‒", "―", "−"])
def test_replaces_all_dash_variants_with_hyphen(self, dash):
assert normalize_typography(f"слово {dash} слово") == "слово - слово"
def test_glues_dash_without_spaces(self):
"""GigaAM пишет «слово—слово» без пробелов."""
assert normalize_typography("это—тестирование") == "это - тестирование"
def test_adds_space_after_opening_quote(self):
"""GigaAM пишет «слово«цитата» без пробела перед кавычкой."""
assert normalize_typography("спрашивает:«По метрике") == "спрашивает: «По метрике"
def test_collapses_multiple_spaces(self):
assert normalize_typography("много пробелов") == "много пробелов"
def test_keeps_hyphen_in_compound_word(self):
assert normalize_typography("look-alike и из-за") == "look-alike и из-за"
class TestSplitSentences:
"""Разметку ролей делает LLM по предложениям: реплику целиком разметить нельзя."""
@staticmethod
def turn(text, start=0.0, end=10.0, loudness=-20.0):
return {"text": text, "start": start, "end": end,
"acoustics": {"loudness_db": loudness}}
def test_splits_on_sentence_endings(self):
out = split_sentences([self.turn("Первое. Второе! Третье? Четвёртое…")])
assert [s["text"] for s in out] == ["Первое.", "Второе!", "Третье?", "Четвёртое…"]
def test_numbering_is_continuous_across_turns(self):
out = split_sentences([self.turn("А. Б."), self.turn("В. Г.")])
assert [s["n"] for s in out] == [1, 2, 3, 4]
def test_turn_index_is_kept(self):
"""По нему собирают диалог обратно после разметки."""
out = split_sentences([self.turn("А. Б."), self.turn("В.")])
assert [s["turn"] for s in out] == [0, 0, 1]
def test_time_is_spread_inside_turn(self):
out = split_sentences([self.turn("А. Б. В.", start=0.0, end=30.0)])
assert [s["start"] for s in out] == [0.0, 10.0, 20.0]
def test_loudness_comes_from_turn(self):
out = split_sentences([self.turn("А. Б.", loudness=-17.5)])
assert all(s["loudness_db"] == -17.5 for s in out)
def test_turn_without_acoustics_gives_none(self):
"""Короткие фрагменты акустику не считают - поле должно быть пустым, не падать."""
out = split_sentences([{"text": "А.", "start": 0.0, "end": 1.0, "acoustics": {}}])
assert out[0]["loudness_db"] is None
def test_text_without_punctuation_stays_one_sentence(self):
out = split_sentences([self.turn("совсем без знаков препинания")])
assert len(out) == 1
def test_empty_text_is_skipped(self):
assert split_sentences([self.turn(" ")]) == []
def test_abbreviation_dots_do_not_split(self):
"""Точка без пробела после неё концом предложения не считается."""
out = split_sentences([self.turn("Нужен документ п.2.1 из списка.")])
assert len(out) == 1