Первоначальная версия VidConf

This commit is contained in:
2026-07-23 01:04:01 +03:00
commit 896455381a
335 changed files with 61527 additions and 0 deletions

View File

@@ -0,0 +1,125 @@
"""Тесты чистой функции реконструкции фраз `build_phrases` (ТЗ §1.3).
Синтетические таймлайны без БД/сети — алгоритм тестируется в изоляции (TDD).
"""
import uuid
from core.plugins.transcriber import Segment
from workers.transcription.phrases import (
INTERJECTION_THRESHOLD_S,
PhraseDraft,
build_phrases,
)
ALICE = uuid.uuid4()
BOB = uuid.uuid4()
def test_single_speaker_multiple_segments_merge_into_one_phrase() -> None:
"""Несколько сегментов одного спикера → одна фраза с границами min start / max end."""
segments = {ALICE: [Segment(0.0, 2.0, "a"), Segment(3.0, 5.0, "b")]}
offsets = {ALICE: 0.0}
result = build_phrases(segments, offsets)
assert result == [PhraseDraft(participant_id=ALICE, start=0.0, end=5.0, text="a b")]
def test_alice_bob_alice_example_from_spec() -> None:
"""Пример ABA из §1.3: три фразы в порядке t_start."""
segments = {
ALICE: [Segment(0.0, 3.0, "alice1"), Segment(6.0, 9.0, "alice2")],
BOB: [Segment(3.0, 6.0, "bob1")],
}
offsets = {ALICE: 0.0, BOB: 0.0}
result = build_phrases(segments, offsets)
assert result == [
PhraseDraft(participant_id=ALICE, start=0.0, end=3.0, text="alice1"),
PhraseDraft(participant_id=BOB, start=3.0, end=6.0, text="bob1"),
PhraseDraft(participant_id=ALICE, start=6.0, end=9.0, text="alice2"),
]
def test_long_overlap_keeps_both_phrases_with_intersecting_intervals() -> None:
"""Перекрытие речи ≥ порога → сохраняются обе фразы с пересекающимися интервалами."""
segments = {
ALICE: [Segment(0.0, 10.0, "alice-long")],
BOB: [Segment(3.0, 8.0, "bob-overlap")],
}
offsets = {ALICE: 0.0, BOB: 0.0}
assert 8.0 - 3.0 >= INTERJECTION_THRESHOLD_S
result = build_phrases(segments, offsets)
assert result == [
PhraseDraft(participant_id=ALICE, start=0.0, end=10.0, text="alice-long"),
PhraseDraft(participant_id=BOB, start=3.0, end=8.0, text="bob-overlap"),
]
# интервалы действительно пересекаются
alice_phrase, bob_phrase = result
assert bob_phrase.start < alice_phrase.end
assert bob_phrase.end > alice_phrase.start
def test_short_interjection_does_not_break_monologue_but_is_kept_separately() -> None:
"""Короткое «угу» (<1.5с) внутри чужого монолога: монолог одной фразой, вставка — отдельной."""
segments = {
ALICE: [Segment(0.0, 2.0, "one"), Segment(2.5, 5.0, "two")],
BOB: [Segment(2.1, 2.3, "угу")],
}
offsets = {ALICE: 0.0, BOB: 0.0}
assert 2.3 - 2.1 < INTERJECTION_THRESHOLD_S
result = build_phrases(segments, offsets)
assert result == [
PhraseDraft(participant_id=ALICE, start=0.0, end=5.0, text="one two"),
PhraseDraft(participant_id=BOB, start=2.1, end=2.3, text="угу"),
]
def test_silence_gap_does_not_break_single_speaker_phrase() -> None:
"""Тишина (большой зазор) между сегментами одного спикера без чужой речи фразу не рвёт."""
segments = {ALICE: [Segment(0.0, 2.0, "a"), Segment(20.0, 22.0, "b")]}
offsets = {ALICE: 0.0}
result = build_phrases(segments, offsets)
assert result == [PhraseDraft(participant_id=ALICE, start=0.0, end=22.0, text="a b")]
def test_empty_input_returns_empty_list() -> None:
"""Пустой вход → пустой список фраз."""
assert build_phrases({}, {}) == []
def test_participant_with_no_segments_contributes_nothing() -> None:
"""Участник без сегментов не создаёт фраз, но не ломает построение остальных."""
segments = {ALICE: [Segment(0.0, 2.0, "a")], BOB: []}
offsets = {ALICE: 0.0, BOB: 0.0}
result = build_phrases(segments, offsets)
assert result == [PhraseDraft(participant_id=ALICE, start=0.0, end=2.0, text="a")]
def test_track_offsets_shift_timeline() -> None:
"""Смещения треков (`track_offsets`) корректно сдвигают таймлайн сегментов."""
segments = {
ALICE: [Segment(0.0, 2.0, "alice-early-on-track")],
BOB: [Segment(0.0, 2.0, "bob-early-on-track")],
}
# трек Боба стартовал позже (offset больше) → на общем таймлайне сеанса
# его сегмент оказывается ПОСЛЕ сегмента Алисы, хотя на своих треках
# оба сегмента начинались с нуля.
offsets = {ALICE: 10.0, BOB: 20.0}
result = build_phrases(segments, offsets)
assert result == [
PhraseDraft(participant_id=ALICE, start=10.0, end=12.0, text="alice-early-on-track"),
PhraseDraft(participant_id=BOB, start=20.0, end=22.0, text="bob-early-on-track"),
]