125 lines
5.2 KiB
Python
125 lines
5.2 KiB
Python
# Copyright 2026 Umbra Tools (umbratools.com)
|
|
# SPDX-License-Identifier: Apache-2.0
|
|
|
|
from __future__ import annotations
|
|
|
|
from umbraaudio.analysis.speaker_confidence import (
|
|
assess_speakers,
|
|
name_quality_issues,
|
|
)
|
|
|
|
|
|
def _spk(name: str, **kw) -> dict:
|
|
base = {
|
|
"primary_name": name,
|
|
"aliases": [],
|
|
"demographics": {"type": "Human", "gender": "Male"},
|
|
"vocal_traits": ["deep"],
|
|
"description": "A character.",
|
|
"alias_evidence": [],
|
|
}
|
|
base.update(kw)
|
|
return base
|
|
|
|
|
|
def _stats(name: str, n: int = 10, flagged: int = 0) -> dict:
|
|
return {name: {"utterances": n, "needs_review": flagged}}
|
|
|
|
|
|
class TestNameQuality:
|
|
def test_narration_verb_prefix(self) -> None:
|
|
issues = name_quality_issues("Walking tallest")
|
|
assert any("narration verb" in i for i in issues)
|
|
|
|
def test_scene_suffix(self) -> None:
|
|
issues = name_quality_issues("Unidentified Speaker (Scene 2)")
|
|
assert any("Scene" in i for i in issues)
|
|
|
|
def test_not_capitalised(self) -> None:
|
|
issues = name_quality_issues("scum leader")
|
|
assert any("capitalised" in i for i in issues)
|
|
|
|
def test_generic_only_name(self) -> None:
|
|
issues = name_quality_issues("The tallest")
|
|
assert any("role/comparative" in i for i in issues)
|
|
|
|
def test_clean_name_has_no_issues(self) -> None:
|
|
assert name_quality_issues("Kainda Walters Foster Murphy") == []
|
|
assert name_quality_issues("Tobias") == []
|
|
|
|
def test_narrator_is_clean(self) -> None:
|
|
assert name_quality_issues("Narrator") == []
|
|
|
|
|
|
class TestDesignation:
|
|
def test_healthy_speaker_is_clear(self) -> None:
|
|
res = assess_speakers([_spk("Tobias")], _stats("Tobias"))
|
|
assert res[0].status == "clear"
|
|
assert res[0].confidence >= 0.75
|
|
|
|
def test_unidentified_is_needs_review(self) -> None:
|
|
spk = _spk("Lieutenant", unidentified=True, unidentified_reason="never named")
|
|
res = assess_speakers([spk], _stats("Lieutenant"))
|
|
assert res[0].status == "needs_review"
|
|
assert any("unidentified" in r for r in res[0].reasons)
|
|
|
|
def test_polluted_name_forces_review_even_if_otherwise_healthy(self) -> None:
|
|
# Everything else is perfect, but the name has a narration verb glued on.
|
|
spk = _spk("Walking tallest")
|
|
res = assess_speakers([spk], _stats("Walking tallest", n=40))
|
|
assert res[0].status == "needs_review"
|
|
assert any("narration verb" in r for r in res[0].reasons)
|
|
|
|
def test_no_dialogue_forces_review(self) -> None:
|
|
res = assess_speakers([_spk("Ghost Speaker")], _stats("Ghost Speaker", n=0))
|
|
assert res[0].status == "needs_review"
|
|
assert any("no dialogue" in r for r in res[0].reasons)
|
|
|
|
def test_duplicate_without_proper_name_is_flagged(self) -> None:
|
|
spk = [_spk("The Girl"), _spk("The Girl helping")]
|
|
res = assess_speakers(spk, _stats("The Girl") | _stats("The Girl helping"))
|
|
by_name = {a.name: a for a in res}
|
|
assert by_name["The Girl"].status == "needs_review"
|
|
assert any("same person" in r for r in by_name["The Girl"].reasons)
|
|
|
|
def test_generic_plus_surname_is_NOT_a_duplicate(self) -> None:
|
|
# "Lieutenant" and "Lieutenant Stonesplitter" are different people.
|
|
spk = [_spk("Lieutenant"), _spk("Lieutenant Stonesplitter")]
|
|
res = assess_speakers(spk, _stats("Lieutenant") | _stats("Lieutenant Stonesplitter"))
|
|
by_name = {a.name: a for a in res}
|
|
assert not any(
|
|
"same person" in r for r in by_name["Lieutenant Stonesplitter"].reasons
|
|
)
|
|
assert not any("same person" in r for r in by_name["Lieutenant"].reasons)
|
|
|
|
def test_comparatives_in_same_group_are_not_duplicates(self) -> None:
|
|
# "Walking oldest" and "Walking Tallest" are two different people.
|
|
spk = [_spk("Walking oldest"), _spk("Walking Tallest")]
|
|
res = assess_speakers(spk, _stats("Walking oldest") | _stats("Walking Tallest"))
|
|
for a in res:
|
|
assert not any("same person" in r for r in a.reasons)
|
|
|
|
def test_typo_duplicate_is_flagged(self) -> None:
|
|
spk = [_spk("Rielan"), _spk("Rieland")]
|
|
res = assess_speakers(spk, _stats("Rielan") | _stats("Rieland"))
|
|
by_name = {a.name: a for a in res}
|
|
assert any("same person" in r for r in by_name["Rielan"].reasons)
|
|
|
|
def test_high_flagged_share_lowers_confidence(self) -> None:
|
|
clean = assess_speakers([_spk("A")], _stats("A", n=20, flagged=0))[0]
|
|
dirty = assess_speakers([_spk("A")], _stats("A", n=20, flagged=16))[0]
|
|
assert dirty.confidence < clean.confidence
|
|
assert dirty.status == "needs_review"
|
|
|
|
def test_every_speaker_gets_a_designation(self) -> None:
|
|
spk = [_spk("A"), _spk("B"), _spk("C")]
|
|
res = assess_speakers(spk, None)
|
|
assert len(res) == 3
|
|
assert all(a.status in ("clear", "needs_review") for a in res)
|
|
|
|
def test_needs_review_sorted_first(self) -> None:
|
|
spk = [_spk("Clean"), _spk("Walking oldest"), _spk("Walking tallest")]
|
|
res = assess_speakers(spk, _stats("Clean") | _stats("Walking oldest") | _stats("Walking tallest"))
|
|
assert res[0].status == "needs_review"
|
|
assert res[-1].name == "Clean"
|