Merge commit '7ce409c580' into dev

This commit is contained in:
DESKTOP-RTLN3BA\$punk 2026-06-16 22:48:14 -07:00
commit 0fe650fd8e
27 changed files with 510 additions and 134 deletions

View file

@ -31,8 +31,8 @@ def make_spec():
language: str = "en",
style: PodcastStyle = PodcastStyle.CONVERSATIONAL,
speakers: list[SpeakerSpec] | None = None,
min_minutes: int = 10,
max_minutes: int = 20,
min_seconds: int = 600,
max_seconds: int = 1200,
focus: str | None = None,
) -> PodcastSpec:
if speakers is None:
@ -54,7 +54,7 @@ def make_spec():
language=language,
style=style,
speakers=speakers,
duration=DurationTarget(min_minutes=min_minutes, max_minutes=max_minutes),
duration=DurationTarget(min_seconds=min_seconds, max_seconds=max_seconds),
focus=focus,
)

View file

@ -66,7 +66,7 @@ def _spec(voice_id: str) -> PodcastSpec:
speakers=[
SpeakerSpec(slot=0, name="Host", role=SpeakerRole.HOST, voice_id=voice_id)
],
duration=DurationTarget(min_minutes=5, max_minutes=10),
duration=DurationTarget(min_seconds=300, max_seconds=600),
)

View file

@ -57,7 +57,7 @@ def test_spec_normalizes_its_language_on_construction():
spec = PodcastSpec(
language="EN-us",
speakers=[_speaker(0)],
duration=DurationTarget(min_minutes=5, max_minutes=10),
duration=DurationTarget(min_seconds=300, max_seconds=600),
)
assert spec.language == "en-us"
@ -68,7 +68,7 @@ def test_speakers_must_have_unique_slots():
PodcastSpec(
language="en",
speakers=[_speaker(0), _speaker(0, voice_id="kokoro:af_bella")],
duration=DurationTarget(min_minutes=5, max_minutes=10),
duration=DurationTarget(min_seconds=300, max_seconds=600),
)
@ -77,7 +77,7 @@ def test_a_brief_needs_at_least_one_speaker():
PodcastSpec(
language="en",
speakers=[],
duration=DurationTarget(min_minutes=5, max_minutes=10),
duration=DurationTarget(min_seconds=300, max_seconds=600),
)
@ -86,7 +86,7 @@ def test_a_monologue_brief_carries_exactly_one_speaker():
language="en",
style=PodcastStyle.MONOLOGUE,
speakers=[_speaker(0)],
duration=DurationTarget(min_minutes=5, max_minutes=10),
duration=DurationTarget(min_seconds=300, max_seconds=600),
)
assert spec.style is PodcastStyle.MONOLOGUE
@ -98,18 +98,25 @@ def test_a_monologue_brief_rejects_multiple_speakers():
language="en",
style=PodcastStyle.MONOLOGUE,
speakers=[_speaker(0), _speaker(1, voice_id="kokoro:af_bella")],
duration=DurationTarget(min_minutes=5, max_minutes=10),
duration=DurationTarget(min_seconds=300, max_seconds=600),
)
def test_duration_rejects_an_inverted_range():
"""A max below the min is a user error caught at the brief gate."""
with pytest.raises(ValidationError):
DurationTarget(min_minutes=20, max_minutes=10)
DurationTarget(min_seconds=1200, max_seconds=600)
def test_duration_midpoint_is_where_drafting_aims():
assert DurationTarget(min_minutes=10, max_minutes=20).midpoint_minutes == 15
assert DurationTarget(min_seconds=600, max_seconds=1200).midpoint_seconds == 900
assert DurationTarget(min_seconds=600, max_seconds=1200).midpoint_minutes == 15
def test_duration_loads_legacy_minute_fields_from_json():
duration = DurationTarget.model_validate({"min_minutes": 10, "max_minutes": 20})
assert duration.min_seconds == 600
assert duration.max_seconds == 1200
def test_blank_focus_becomes_absent():
@ -117,7 +124,7 @@ def test_blank_focus_becomes_absent():
spec = PodcastSpec(
language="en",
speakers=[_speaker(0)],
duration=DurationTarget(min_minutes=5, max_minutes=10),
duration=DurationTarget(min_seconds=300, max_seconds=600),
focus=" ",
)
assert spec.focus is None
@ -127,7 +134,7 @@ def test_speaker_for_returns_the_speaker_bound_to_a_slot():
spec = PodcastSpec(
language="en",
speakers=[_speaker(0), _speaker(1, voice_id="kokoro:af_bella")],
duration=DurationTarget(min_minutes=5, max_minutes=10),
duration=DurationTarget(min_seconds=300, max_seconds=600),
)
assert spec.speaker_for(1).voice_id == "kokoro:af_bella"
@ -136,7 +143,7 @@ def test_speaker_for_raises_when_no_speaker_matches():
spec = PodcastSpec(
language="en",
speakers=[_speaker(0)],
duration=DurationTarget(min_minutes=5, max_minutes=10),
duration=DurationTarget(min_seconds=300, max_seconds=600),
)
with pytest.raises(KeyError):
spec.speaker_for(99)

View file

@ -105,7 +105,7 @@ async def test_ainvoke_propagates_quota_insufficient_error(monkeypatch):
async def _denying_billable_call(**_kwargs):
raise QuotaInsufficientError(
usage_type="vision_extraction",
balance_micros=0,
balance_micros=5_000_000,
remaining_micros=0,
)
yield # unreachable but required for asynccontextmanager type

View file

@ -98,7 +98,7 @@ async def _denying_billable_call(**kwargs):
_CALL_LOG.append(kwargs)
raise QuotaInsufficientError(
usage_type=kwargs.get("usage_type", "?"),
balance_micros=0,
balance_micros=5_000_000,
remaining_micros=0,
)
yield SimpleNamespace() # pragma: no cover