From 8ed1f122a39c1d2179014370808f0bf589cb2693 Mon Sep 17 00:00:00 2001 From: mARTin-B78 Date: Fri, 4 Sep 2026 09:18:41 +0200 Subject: [PATCH] test: add book two attribution fixture --- CHANGELOG.md | 2 ++ TODO.md | 5 +++-- VERSION | 2 +- tests/fixtures/book2_attribution.json | 5 +++++ tests/test_conversation.py | 12 ++++++++++++ 5 files changed, 23 insertions(+), 3 deletions(-) create mode 100644 tests/fixtures/book2_attribution.json diff --git a/CHANGELOG.md b/CHANGELOG.md index 1d67c1e..82059bd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -33,6 +33,8 @@ Follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) · versioned wi status/cancellation endpoints with queue state, timeout and failure handling; existing synchronous callers remain compatible. - **Optional Background Casting.** `/api/match-characters-voices` now accepts `background: true` and exposes bounded job status/cancellation endpoints while reusing the existing casting validation, retries, and idempotency behavior. +- **Attribution Fixture Coverage.** Added a neutral multi-speaker Book 2-style fixture regression to verify parser + normalization across named speakers and narration without requiring a live provider. - **STT Cancellation Safety.** Queued transcription cancels before consuming the single STT slot, and timed-out requests keep the device slot held until the underlying worker actually exits. - **Credential Boundary Review.** Provider credentials are now documented as server-only; only the explicitly diff --git a/TODO.md b/TODO.md index 1bfe909..24bab3a 100644 --- a/TODO.md +++ b/TODO.md @@ -65,8 +65,9 @@ the first block before investing in architecture or polish. - [ ] **L — Add background jobs and bounded queues.** Image generation already has the full interactive job path, and transcription, voice design, and casting now have optional ID/status/cancel/timeout paths while preserving synchronous callers. Durable persistence and retry policy still need to be added. -- [ ] **L — Run the Book 2 generalization regression.** Confirm speaker-attribution and - confidence rules are not overfit to *Der Sturm*. +- [ ] **L — Run the Book 2 generalization regression.** Added a neutral multi-speaker fixture and parser regression; + a real Book 2 auto-cast/attribution output still needs to be captured and reviewed to confirm rules are not overfit + to *Der Sturm*. - [x] **L — Add character forms inside one character record.** `sheet.forms[]` stores bounded alternate appearances, prompts, and per-form visual assets; the detail-page switcher preserves one character ID and voice, and selected forms feed prompt generation and identity fingerprints. diff --git a/VERSION b/VERSION index 013173a..87b26e8 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.22.6 +1.22.7 diff --git a/tests/fixtures/book2_attribution.json b/tests/fixtures/book2_attribution.json new file mode 100644 index 0000000..2880bb6 --- /dev/null +++ b/tests/fixtures/book2_attribution.json @@ -0,0 +1,5 @@ +{ + "book": "book-2-neutral-fixture", + "known_characters": ["Captain Vale", "Mira", "The Ferryman"], + "raw": "{\"segments\":[{\"speaker\":\"Narrator\",\"type\":\"narration\",\"text\":\"At dawn the ferry reached the northern lock.\",\"emotion\":\"\"},{\"speaker\":\"Captain Vale\",\"type\":\"dialogue\",\"text\":\"Keep the lanterns covered.\",\"emotion\":\"cautious\"},{\"speaker\":\"The Ferryman\",\"type\":\"dialogue\",\"text\":\"Then we cross before the tide turns.\",\"emotion\":\"firm\"},{\"speaker\":\"Mira\",\"type\":\"dialogue\",\"text\":\"I can hear bells beyond the reeds.\",\"emotion\":\"alert\"}]}" +} diff --git a/tests/test_conversation.py b/tests/test_conversation.py index ed88f1d..3e07238 100644 --- a/tests/test_conversation.py +++ b/tests/test_conversation.py @@ -2,6 +2,7 @@ from __future__ import annotations import os +import json import tempfile import unittest @@ -51,6 +52,17 @@ class AttributionParserTests(unittest.TestCase): self.assertTrue(dialogue["text"].endswith("Land. Lieblich bisweilen, mild und voll der guten Menschen. ‹")) self.assertEqual(narration["text"], "Zitiert aus dem Buch.") + def test_book2_neutral_fixture_preserves_multiple_speakers(self): + fixture_path = os.path.join(os.path.dirname(__file__), "fixtures", "book2_attribution.json") + with open(fixture_path, encoding="utf-8") as handle: + fixture = json.load(handle) + result = _attribution_parse(fixture["raw"], " ".join(fixture["known_characters"])) + self.assertEqual( + [segment["speaker"] for segment in result["segments"]], + ["Narrator", "Captain Vale", "The Ferryman", "Mira"], + ) + self.assertTrue(all(segment["type"] == "dialogue" for segment in result["segments"][1:])) + def test_consistent_image_job_can_be_cancelled(self): from fastapi.testclient import TestClient from server import app