117 lines
4.2 KiB
Python
117 lines
4.2 KiB
Python
# 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
|