122 lines
6.1 KiB
Python
122 lines
6.1 KiB
Python
"""片段幂等更新和说话人安全规则的回归测试。"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import unittest
|
|
|
|
from speaker_assembler import SegmentAssembler
|
|
|
|
|
|
class SegmentAssemblerTests(unittest.TestCase):
|
|
def test_late_speaker_updates_merge_and_split_existing_blocks(self):
|
|
"""模拟 final 先到、声纹后到以及身份修正,按当前完整状态重新分块。"""
|
|
assembler = SegmentAssembler()
|
|
for sid in range(3):
|
|
assembler.apply_sentence({"sentence_id": sid, "sentence": str(sid), "sentence_type": 1,
|
|
"start_time": sid * 2000, "end_time": sid * 2000 + 1000})
|
|
for sid in range(3):
|
|
assembler.apply_speaker_update({"sentence_id": sid, "speaker_id": 0, "speaker_name": "A",
|
|
"speaker_evidence": "fresh", "speaker_confidence": 0.9})
|
|
self.assertEqual(assembler.display_blocks()[0]["segment_ids"], [0, 1, 2])
|
|
assembler.apply_speaker_update({"sentence_id": 1, "speaker_id": 1, "speaker_name": "B",
|
|
"speaker_evidence": "fresh", "speaker_confidence": 0.9})
|
|
self.assertEqual([b["speaker_id"] for b in assembler.display_blocks()], [0, 1, 0])
|
|
self.assertEqual(len(assembler.display_blocks(False)), 3)
|
|
|
|
def test_speaker_update_cannot_inherit_or_change_text_and_timestamps(self):
|
|
"""异步更新同样要拒绝继承证据,且模型响应不能改写原始转写。"""
|
|
assembler = SegmentAssembler()
|
|
assembler.apply_sentence({"sentence_id": 0, "sentence": "原文", "start_time": 0, "end_time": 500})
|
|
updated = assembler.apply_speaker_update({
|
|
"sentence_id": 0, "sentence": "错误原文", "start_time": 9000,
|
|
"speaker_id": 7, "speaker_name": "前一位", "user_id": "person-a",
|
|
"speaker_strategy": "short_attach", "speaker_evidence": "confirmed", "speaker_confidence": 0.99,
|
|
"_embedding": [1, 0],
|
|
})
|
|
self.assertEqual(updated["speaker_id"], -1)
|
|
self.assertEqual(updated["sentence"], "原文")
|
|
self.assertEqual(updated["start_time"], 0)
|
|
self.assertNotIn("user_id", updated)
|
|
self.assertNotIn("_embedding", updated)
|
|
|
|
def test_nonfinite_or_weak_confidence_is_not_trusted(self):
|
|
"""NaN/Infinity 和不充分证据不得进入可信展示块。"""
|
|
for confidence in (float("nan"), float("inf"), 0.59):
|
|
assembler = SegmentAssembler()
|
|
assembler.apply_sentence({"sentence_id": 0, "sentence": "test"})
|
|
result = assembler.apply_speaker_update({"sentence_id": 0, "speaker_id": 0,
|
|
"speaker_evidence": "fresh", "speaker_confidence": confidence})
|
|
self.assertEqual(result["speaker_status"], "evidence_rejected")
|
|
self.assertEqual(assembler.display_blocks()[0]["speaker_id"], -1)
|
|
|
|
def test_same_cluster_different_named_identity_does_not_merge(self):
|
|
"""相同匿名簇 ID 不代表相同注册身份,弱匿名段不能挤入实名块。"""
|
|
assembler = SegmentAssembler()
|
|
for sid, user in enumerate(("A", "B", None)):
|
|
assembler.apply_sentence({"sentence_id": sid, "sentence": str(sid), "speaker_id": 0,
|
|
"speaker_evidence": "fresh", "speaker_confidence": 0.9, "user_id": user})
|
|
self.assertEqual(len(assembler.display_blocks()), 3)
|
|
|
|
def test_text_only_update_preserves_identity_and_final_is_not_rolled_back(self):
|
|
"""文本与声纹独立更新;重发的旧 partial 不得回滚 final。"""
|
|
assembler = SegmentAssembler()
|
|
assembler.apply_sentence({"sentence_id": 0, "sentence": "final", "sentence_type": 1})
|
|
assembler.apply_speaker_update({"sentence_id": 0, "speaker_id": 0,
|
|
"speaker_evidence": "fresh", "speaker_confidence": 0.9})
|
|
assembler.apply_sentence({"sentence_id": 0, "sentence": "corrected", "sentence_type": 1})
|
|
assembler.apply_sentence({"sentence_id": 0, "sentence": "old", "sentence_type": 0})
|
|
self.assertEqual(assembler.raw_snapshot()[0]["sentence"], "corrected")
|
|
self.assertEqual(assembler.display_blocks()[0]["speaker_id"], 0)
|
|
|
|
def test_same_sentence_id_is_updated_in_place(self) -> None:
|
|
assembler = SegmentAssembler()
|
|
assembler.apply_sentence({"sentence_id": 3, "sentence": "你好", "sentence_type": 0})
|
|
final = assembler.apply_sentence({"sentence_id": 3, "sentence": "你好,今天开始。", "sentence_type": 1})
|
|
|
|
self.assertEqual(len(assembler.raw_snapshot()), 1)
|
|
self.assertEqual(final["sentence_type"], 1)
|
|
self.assertEqual(final["revision_count"], 1)
|
|
|
|
def test_short_inherited_name_is_pending_and_embedding_is_dropped(self) -> None:
|
|
assembler = SegmentAssembler()
|
|
segment = assembler.apply_sentence(
|
|
{
|
|
"sentence_id": 1,
|
|
"sentence": "嗯",
|
|
"start_time": 0,
|
|
"end_time": 900,
|
|
"speaker_id": 7,
|
|
"speaker_name": "上一位",
|
|
"speaker_strategy": "short_attach",
|
|
"speaker_evidence": "confirmed",
|
|
"speaker_confidence": 0.99,
|
|
"_embedding": [1, 2, 3],
|
|
}
|
|
)
|
|
|
|
self.assertEqual(segment["speaker_id"], -1)
|
|
self.assertEqual(segment["speaker_evidence"], "pending")
|
|
self.assertNotIn("_embedding", segment)
|
|
|
|
def test_a_b_a_keeps_three_time_ordered_blocks(self) -> None:
|
|
assembler = SegmentAssembler()
|
|
for sentence_id, speaker_id, start in ((1, 10, 0), (2, 20, 2000), (3, 10, 4000)):
|
|
assembler.apply_sentence(
|
|
{
|
|
"sentence_id": sentence_id,
|
|
"sentence": f"句子{sentence_id}",
|
|
"start_time": start,
|
|
"end_time": start + 1000,
|
|
"speaker_id": speaker_id,
|
|
"speaker_evidence": "confirmed",
|
|
"speaker_confidence": 0.9,
|
|
}
|
|
)
|
|
|
|
blocks = assembler.display_blocks()
|
|
self.assertEqual([block["speaker_id"] for block in blocks], [10, 20, 10])
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|