Durable curation: persisted splits + pre-resolve title edits, catalog A→Z

Wiz-War had no split button: can_split required a matches row, but fresh
extractions leave multi-photo titles rowless until resolve runs. Splits
are now a title-level decision persisted in data/title_splits.json,
honored by extract's dedupe and resolve's dedupe on every rebuild, with
the button on any multi-photo line — resolved or not.

Same mechanism carries human corrections: data/title_edits.json stores
fixed misreads and known cues (publisher/edition/year/language), applied
before dedupe on every titles.json rebuild, editable from a new inline
form on every catalog line. An edit drops the title's stale matches rows
so resolve re-queries with the corrected data.

The catalog page now sorts alphabetically (case-insensitive; split
copies stay adjacent) instead of extraction order.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016jXZFSTZQKzAC8fqpWSz9g
This commit is contained in:
Eric Wagoner
2026-08-02 19:47:08 -04:00
parent 24a7bcb9e0
commit a7f0cfee05
12 changed files with 641 additions and 28 deletions
+9
View File
@@ -439,6 +439,15 @@ def test_dedupe_versions_must_agree():
assert dedupe_matches(rows3, []) == []
def test_dedupe_matches_skips_split_titles():
rows = [
_mrow("Wiz-War", "94", "a.jpg"),
_mrow("Wiz-War", "94", "b.jpg"),
]
assert dedupe_matches(rows, [], split_titles={"wiz war"}) == []
assert all(not r["merged_into"] for r in rows)
def test_dedupe_is_idempotent_and_skips_merged():
rows = [
_mrow("Jokin Ha...", "193621", "a.jpg"),