fix(characters): do not flag a character reusing its own picture across books
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
parent
4ab345d1c1
commit
a280ddacde
@ -11,6 +11,7 @@ Follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) · versioned wi
|
|||||||
- **Verify / recast for span-mode casts** (`audiobookVerifySpans`): the app's Recast unknown, 2nd Quality Run and auto-repair entry points now run span-natively for a span cast instead of the classic passes that re-emit text. **Verify is advisory**: one extra differently-windowed pass (~10 min per novel) that never changes a speaker — agreeing lines are marked `agreed`, disagreeing lines keep the first answer, are marked `disputed` and record the other pass's answer as `altSpeaker`; the confidence score reflects both. **Recast unknown** re-asks only the Unknown lines with reasoning and fuller context (`attribute_only` on `/api/attribute-spans`) and leaves any line the model cannot place Unknown. Segments now carry a `spanId`.
|
- **Verify / recast for span-mode casts** (`audiobookVerifySpans`): the app's Recast unknown, 2nd Quality Run and auto-repair entry points now run span-natively for a span cast instead of the classic passes that re-emit text. **Verify is advisory**: one extra differently-windowed pass (~10 min per novel) that never changes a speaker — agreeing lines are marked `agreed`, disagreeing lines keep the first answer, are marked `disputed` and record the other pass's answer as `altSpeaker`; the confidence score reflects both. **Recast unknown** re-asks only the Unknown lines with reasoning and fuller context (`attribute_only` on `/api/attribute-spans`) and leaves any line the model cannot place Unknown. Segments now carry a `spanId`.
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
- **Visual-pair review no longer flags the same character reusing its picture in a later book** (`asset_reused_across_characters` now fires only when *different names* share one image). On the real library this removed 6 of 14 false-alarm groups (Darrag, Himgi, Lysandra, Marcian, Nyrilla, Narrator); the remaining ones are aliases or mix-ups worth a look (e.g. Alrik/Ritter/Zerwas share one image).
|
||||||
- Span cast and verify now send the model **warm-up request** first (as the classic engine does), so a cold-booting model server no longer times out the first passage into tag guessing.
|
- Span cast and verify now send the model **warm-up request** first (as the classic engine does), so a cold-booting model server no longer times out the first passage into tag guessing.
|
||||||
- The cast view's **2.4 Verify all characters** now reaches the advisory second pass for span casts (it previously passed only `repairNarration`, which would have run the Unknown-only recast); its tooltip describes what it does for a span cast.
|
- The cast view's **2.4 Verify all characters** now reaches the advisory second pass for span casts (it previously passed only `repairNarration`, which would have run the Unknown-only recast); its tooltip describes what it does for a span cast.
|
||||||
|
|
||||||
|
|||||||
@ -176,9 +176,12 @@ async def characters_visual_pair_review():
|
|||||||
if form_id:
|
if form_id:
|
||||||
report["form_id"], report["form_name"] = form_id, form_name
|
report["form_id"], report["form_name"] = form_id, form_name
|
||||||
reports.append(report)
|
reports.append(report)
|
||||||
|
names_by_id = {r.get("id"): re.sub(r"\s+", " ", str(r.get("name") or "")).strip().lower() for r in records}
|
||||||
for owners in digest_owners.values():
|
for owners in digest_owners.values():
|
||||||
character_ids = {owner.rsplit(":", 1)[0] for owner in owners}
|
character_ids = {owner.rsplit(":", 1)[0] for owner in owners}
|
||||||
if len(character_ids) > 1:
|
# The same character carried into a later book legitimately keeps its picture; only
|
||||||
|
# different names sharing one image (a copy/paste or mix-up) is worth reviewing.
|
||||||
|
if len({names_by_id.get(cid) for cid in character_ids}) > 1:
|
||||||
for owner in owners:
|
for owner in owners:
|
||||||
char_id = owner.rsplit(":", 1)[0]
|
char_id = owner.rsplit(":", 1)[0]
|
||||||
report = next((item for item in reports if item["id"] == char_id and "form_id" not in item), None)
|
report = next((item for item in reports if item["id"] == char_id and "form_id" not in item), None)
|
||||||
|
|||||||
@ -153,6 +153,13 @@ class VisualPairReviewTests(unittest.TestCase):
|
|||||||
"forms": [{"id": "f", "name": "F", "physical": "x"}]}}
|
"forms": [{"id": "f", "name": "F", "physical": "x"}]}}
|
||||||
self.assertEqual(self.review([rec])["issues"], [])
|
self.assertEqual(self.review([rec])["issues"], [])
|
||||||
|
|
||||||
|
def test_the_same_character_reusing_its_picture_in_another_book_is_not_flagged(self):
|
||||||
|
s = {"physical": "a"}
|
||||||
|
recs = [{"id": f"b{i}::darrag", "book": f"Book {i}", "name": "Darrag", "image": _img(9),
|
||||||
|
"sheet": {**s, **_pair_meta(s), "concept_art_image": _img(20 + i)}} for i in range(2)]
|
||||||
|
recs[1]["name"] = " darrag "
|
||||||
|
self.assertEqual(self.review(recs)["issues"], [])
|
||||||
|
|
||||||
def test_the_same_asset_on_two_characters_is_flagged(self):
|
def test_the_same_asset_on_two_characters_is_flagged(self):
|
||||||
s = {"physical": "a"}
|
s = {"physical": "a"}
|
||||||
recs = [{"id": f"c{i}", "name": f"N{i}", "image": _img(9),
|
recs = [{"id": f"c{i}", "name": f"N{i}", "image": _img(9),
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user