fix: a refusal that said nothing had changed

Comparing marker and number *sets* reported 'lost -, added -' whenever a
token merely appeared a different number of times — printed on the line
explaining why the article was refused, which read as a contradiction.
It counts repeats now: 'cross-references changed (duplicated 396x1)',
which is what actually happened to Cystic Fibrosis.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TqXevQJhxFrM7jJg82cgZN
This commit is contained in:
Daniel 2026-09-11 13:47:39 +02:00
parent e89e9f6f70
commit 9b7ab72038

View file

@ -29,6 +29,7 @@ import json
import re import re
import sys import sys
import uuid import uuid
from collections import Counter
from pathlib import Path from pathlib import Path
from sqlalchemy.orm.attributes import flag_modified from sqlalchemy.orm.attributes import flag_modified
@ -105,6 +106,26 @@ def conforms(article) -> bool:
return all(required in titles for required in REQUIRED) return all(required in titles for required in REQUIRED)
def _multiset_problem(what: str, before: list[str], after: list[str]) -> list[str]:
"""Describe how two multisets differ, counting repeats.
Comparing sets alone reported "lost -, added -" when a token simply appeared
a different number of times which reads as nothing having changed, on a
line explaining a refusal.
"""
want, got = Counter(before), Counter(after)
if want == got:
return []
lost = {k: want[k] - got.get(k, 0) for k in want if want[k] > got.get(k, 0)}
gained = {k: got[k] - want.get(k, 0) for k in got if got[k] > want.get(k, 0)}
parts = []
if lost:
parts.append("lost " + ", ".join(f"{k}x{n}" for k, n in sorted(lost.items())[:6]))
if gained:
parts.append("duplicated " + ", ".join(f"{k}x{n}" for k, n in sorted(gained.items())[:6]))
return [f"{what} changed ({'; '.join(parts)})"]
def validate(before: list[dict], after: list[dict]) -> list[str]: def validate(before: list[dict], after: list[dict]) -> list[str]:
"""Reasons this restructure must not be applied. Empty means it is safe.""" """Reasons this restructure must not be applied. Empty means it is safe."""
problems = [] problems = []
@ -128,15 +149,10 @@ def validate(before: list[dict], after: list[dict]) -> list[str]:
if not new_text.strip(): if not new_text.strip():
return ["empty"] return ["empty"]
want, got = sorted(MARKER.findall(old_text)), sorted(MARKER.findall(new_text)) problems += _multiset_problem("cross-references", MARKER.findall(old_text),
if want != got: MARKER.findall(new_text))
problems.append(f"cross-references changed (lost {sorted(set(want) - set(got)) or '-'}, " problems += _multiset_problem("numbers", NUMBER.findall(facts_only(old_text)),
f"added {sorted(set(got) - set(want)) or '-'})") NUMBER.findall(facts_only(new_text)))
want_n = sorted(NUMBER.findall(facts_only(old_text)))
got_n = sorted(NUMBER.findall(facts_only(new_text)))
if want_n != got_n:
problems.append(f"numbers changed (lost {sorted(set(want_n) - set(got_n)) or '-'}, "
f"added {sorted(set(got_n) - set(want_n)) or '-'})")
ratio = len(new_text) / max(len(old_text), 1) ratio = len(new_text) / max(len(old_text), 1)
if not MIN_RATIO <= ratio <= MAX_RATIO: if not MIN_RATIO <= ratio <= MAX_RATIO:
problems.append(f"length {ratio:.2f}x of the original") problems.append(f"length {ratio:.2f}x of the original")