Catalog pairs same-title rows by photos, not csv position
The Wiz-War lines were displaying each other's rows: an edit re-queue recreates its row at the END of matches.csv, and the catalog's positional per-title pairing then crossed the wires — the 4504 line wore 4528's open ballot while 4528's line offered 4504's pick-edition button (whose click re-targeted by photos and safely hit the other row, deepening the confusion). Pairing now matches run_resolve's rule: exact photo set, then overlap, then positional fallback, with unclaimed rows appended as their own lines. Plus a regression test with deliberately reversed csv order, and the open-ballot marker restyled as a quiet dotted link instead of a mis-wrapped button. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016jXZFSTZQKzAC8fqpWSz9g
This commit is contained in:
co-authored by
Claude Fable 5
parent
c94e8bc695
commit
3e61a03686
@@ -324,6 +324,11 @@ button.danger { color: var(--stop-ink); border-color: var(--stop); background: #
|
||||
.catalog a:hover { color: var(--accent-ink); }
|
||||
.catalog td.actions { text-align: right; }
|
||||
.catalog td.actions button { margin: .1rem 0 .1rem .3rem; white-space: nowrap; }
|
||||
.catalog td.actions .ballotlink {
|
||||
display: inline-block; margin: .1rem 0 .1rem .3rem; white-space: nowrap;
|
||||
font-size: .78rem; color: var(--accent-ink);
|
||||
text-decoration: underline dotted; text-underline-offset: 3px;
|
||||
}
|
||||
.catalog td.actions button { font-size: .78rem; padding: .2rem .6rem; box-shadow: none; }
|
||||
.catalog td.actions button:hover { background: var(--board); }
|
||||
.catalog tr.editrow td { background: #fff; border-top: none; padding: .2rem .5rem .7rem; }
|
||||
|
||||
@@ -90,8 +90,8 @@ function render() {
|
||||
split into copies</button>`
|
||||
: ""}
|
||||
${c.version_status === "version_ambiguous"
|
||||
? `<a class="linkbtn ballot" href="/review#editions"
|
||||
title="this copy's edition ballot is open">ballot in Review</a>`
|
||||
? `<a class="ballotlink" href="/review#editions"
|
||||
title="this copy's edition ballot is open">ballot in Review →</a>`
|
||||
: ""}
|
||||
${c.bgg_id && ["auto", "approved"].includes(c.status)
|
||||
&& ["version_unknown", "version_error", ""].includes(c.version_status || "")
|
||||
|
||||
+31
-14
@@ -534,7 +534,6 @@ def create_app(
|
||||
rows_by_title: dict[str, list[dict]] = {}
|
||||
for r in session.rows:
|
||||
rows_by_title.setdefault(r["title_raw"], []).append(r)
|
||||
title_seen: Counter[str] = Counter()
|
||||
catalog = []
|
||||
|
||||
def catalog_line(entry, row) -> dict:
|
||||
@@ -579,25 +578,43 @@ def create_app(
|
||||
"shaky": bool(entry and not row and entry.confidence != "high"),
|
||||
}
|
||||
|
||||
for entry in session.titles:
|
||||
# photo-overlap pairing first, positional fallback — the same rule
|
||||
# as run_resolve. Positional-only broke the moment an edited copy's
|
||||
# row was recreated at the END of matches.csv: same-title lines
|
||||
# displayed each other's rows (buttons and ballots swapped owners)
|
||||
claimed: set[int] = set()
|
||||
|
||||
def row_for(entry) -> dict | None:
|
||||
same_title = rows_by_title.get(entry.title_raw, [])
|
||||
ix = title_seen[entry.title_raw]
|
||||
title_seen[entry.title_raw] += 1
|
||||
# positional pairing: the ix-th entry of a title reports the
|
||||
# ix-th row of that title (run_resolve pairs photo-overlap
|
||||
# first, but split entries and split rows both keep sorted
|
||||
# photo order, so the ordinals line up)
|
||||
row = same_title[ix] if ix < len(same_title) else None
|
||||
entry_photos = set(entry.source_photos)
|
||||
|
||||
def photos_of(r: dict) -> set[str]:
|
||||
return {p for p in r["source_photos"].split(";") if p}
|
||||
|
||||
for match in (
|
||||
lambda r: photos_of(r) == entry_photos,
|
||||
lambda r: bool(photos_of(r) & entry_photos),
|
||||
lambda r: True,
|
||||
):
|
||||
for r in same_title:
|
||||
if id(r) not in claimed and match(r):
|
||||
claimed.add(id(r))
|
||||
return r
|
||||
return None
|
||||
|
||||
for entry in session.titles:
|
||||
row = row_for(entry)
|
||||
# a split row's photo set is narrower than its entry's — show
|
||||
# the row's own photos for split copies
|
||||
catalog.append(
|
||||
catalog_line(None if row and row.get("dedupe_veto") else entry, row)
|
||||
)
|
||||
# surplus rows beyond the entry count — split copies — are real
|
||||
# physical games and get their own catalog lines
|
||||
for title, rows_for_title in rows_by_title.items():
|
||||
for row in rows_for_title[title_seen[title] :]:
|
||||
catalog.append(catalog_line(None, row))
|
||||
# rows no entry claimed — split copies — are real physical games
|
||||
# and get their own catalog lines
|
||||
for rows_for_title in rows_by_title.values():
|
||||
for row in rows_for_title:
|
||||
if id(row) not in claimed:
|
||||
catalog.append(catalog_line(None, row))
|
||||
unresolved_count = sum(
|
||||
1 for e in session.titles if e.title_raw not in resolved_titles
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user