Files
UmbraAudio/tests/test_designation_scan.py
2026-10-18 19:01:04 -07:00

117 lines
4.2 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.

# Copyright 2026 Umbra Tools (umbratools.com)
# SPDX-License-Identifier: Apache-2.0
from __future__ import annotations
from umbraaudio.analysis.designation_scan import (
scan_book_designations,
scan_chapter_designations,
)
# The real Last Dwarf ch5 passage that discovery kept dropping speakers from.
CH5 = (
"There are six young men of varying ages. They are still behind Hallr. "
"Listening, Hallr can make out what they say. The oldest says, “I’m sure "
"that is a dwarf.” “I thought they died off,” says the tallest. The young "
"one blurts, “Obviously not.” “Do we kill it?” asks the one with the "
"biggest sword. The biggest one, a healthy farm boy, says, “Hell yes.” "
"Hallr says, “I am Hallr.”"
)
def _names(text: str) -> set[str]:
return {h.designation for h in scan_chapter_designations(text, 1)}
class TestCh5Crowd:
def test_captures_all_six_designations(self) -> None:
names = _names(CH5)
for expected in [
"The Oldest",
"The Tallest",
"The Young One",
"The One With The Biggest Sword",
"The Biggest One",
]:
assert expected in names, f"missing {expected}: {sorted(names)}"
def test_captures_named_speaker(self) -> None:
assert "Hallr" in _names(CH5)
class TestCleanUp:
def test_possessive_stripped(self) -> None:
text = "Hallr’s eyes narrowed. “No,” Hallr’s said."
assert not any("’" in n for n in _names(text))
def test_possessive_voice_becomes_person(self) -> None:
assert "Sheyla" in _names("Sheyla’s voice answered, “No.”")
class TestExclusions:
def test_pronouns_excluded(self) -> None:
names = _names("He said, “No.” She replied, “Yes.” They left.")
assert "He" not in names
assert "She" not in names
assert "They" not in names
def test_indefinite_excluded(self) -> None:
names = _names("Another said, “Wait.” People left. No One answered.")
assert "Another" not in names
assert "People" not in names
assert "No One" not in names
def test_non_speech_verbs_not_attribution(self) -> None:
# "walked past" is not a speech attribution
names = _names("The old man walked past the door.")
assert "Old" not in names
class TestQuotedNarration:
def test_speech_tag_inside_a_quote_is_not_a_speaker(self) -> None:
# "Mama said I had hair like hers" is Mama being *talked about*.
text = '“Your hair always been wild like this?” “Mama said I had hair like hers.”'
assert "Mama" not in _names(text)
def test_real_tag_after_a_quote_is_still_caught(self) -> None:
text = '“No,” the old smith said. “Not today.”'
assert any("Smith" in n for n in _names(text))
class TestFiniteVerbs:
def test_clause_is_not_a_designation(self) -> None:
# "Treasure is one answer." — "answer" is a noun here, not a verb.
names = _names('“I’m not sure. Treasure is one answer. Another?”')
assert "Treasure Is One" not in names
class TestEvidence:
def test_evidence_is_recorded(self) -> None:
hits = {h.designation: h for h in scan_chapter_designations(CH5, 7)}
assert hits["The Oldest"].chapter_id == 7
assert hits["The Oldest"].evidence
assert "oldest" in hits["The Oldest"].evidence.lower()
def test_empty_text(self) -> None:
assert scan_chapter_designations("", 1) == []
class TestBookScan:
def test_merges_across_chapters(self) -> None:
chapters = [
{"id": 1, "source_text": "The oldest said, “No.”"},
{"id": 2, "source_text": "The tallest replied, “Yes.”"},
{"id": 3, "source_text": ""},
]
out = scan_book_designations(chapters)
assert set(out) == {"the oldest", "the tallest"}
def test_dedupes_same_designation_across_chapters(self) -> None:
chapters = [
{"id": 1, "source_text": "The Guard said, “Halt.”"},
{"id": 2, "source_text": "The Guard said, “Move.”"},
]
out = scan_book_designations(chapters)
assert len(out) == 1
assert out["the guard"].chapter_id == 1