Remove-from-catalog: a third durable curation store

Titles that aren't games (misread box art, out-of-scope items) can now
be removed outright: a danger button in the catalog's edit panel posts
/api/remove-title, which drops the line's matches rows (veto'd ones
too — removal is the human explicitly discarding the line), records the
decision photo-scoped in data/title_removals.json, and replays
titles.json. Every rebuild filters removed sightings after edits and
before dedupe, so re-extraction cannot resurrect them; undo by deleting
the record from the store. The three stores now share one scoped-record
parser and recorder.

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 20:15:46 -04:00
parent 86d434a400
commit 626f255c01
10 changed files with 187 additions and 17 deletions
+17
View File
@@ -24,6 +24,7 @@ from bggpipe.extract import (
replay_titles,
run_extract,
)
from bggpipe.normalize import normalize_title
def _write_image(path, size=(400, 300), fmt="JPEG", color=(200, 30, 30)):
@@ -234,6 +235,22 @@ def test_edits_chain_and_target_photos():
assert entries[1] == _entry("Wiz-War", "b.jpg") # untargeted copy untouched
def test_removals_filter_rebuilds_photo_scoped():
from bggpipe.extract import apply_title_removals
entries = [
_entry("Not A Game", "a.jpg"),
_entry("Not A Game", "b.jpg"),
_entry("Catan", "a.jpg"),
]
removals = [{"norm": normalize_title("Not A Game"), "photos": {"a.jpg"}}]
kept = apply_title_removals(entries, removals)
assert [(e["title_raw"], e["source_photos"]) for e in kept] == [
("Not A Game", ["b.jpg"]), # other sighting untouched
("Catan", ["a.jpg"]),
]
def test_stores_roundtrip_and_replay_from_raw(tmp_path):
cfg = Config(data_dir=tmp_path / "data", photos_dir=tmp_path / "photos")
raw = cfg.extract_raw_dir