sorter: drop non-warhammer game systems (default: Middle-Earth)

The collection is warhammer-only; everything else stays in _unsorted
even when names match the catalog. The catalog snapshot keeps all
systems - the filter is a sorter default (DROPPED_SYSTEMS, applied in
load_catalog and after keyword classification), adjustable with
--drop-system/--keep-system.
This commit is contained in:
2026-10-07 08:03:05 +02:00
parent e13412a5fe
commit 0439071f70
3 changed files with 68 additions and 4 deletions
+26 -2
View File
@@ -32,9 +32,15 @@ UNSORTED = "_unsorted"
UNCLASSIFIED_CLASS = "Unclassified"
# A file whose system the keyword tables could not identify only gets a
# catalog unit when the fuzzy hit is strong - otherwise it would wander
# into other games' sections (Middle-earth, Blood Bowl, ...) on a guess.
# into other games' sections (Blood Bowl, ...) on a guess.
CROSS_SYSTEM_MIN = 0.75
# Game systems whose sections the sorter must never place into. The
# collection is warhammer-only: everything else (Middle-earth proxies,
# ...) stays in _unsorted even when a name matches the catalog. The
# catalog snapshot itself keeps all systems; the filter lives here.
DROPPED_SYSTEMS = frozenset({"Middle-Earth"})
# Weak acceptance tier: when nothing reached the normal threshold, a hit
# at least this strong is accepted but flagged low confidence ("[w]" in
# the plan, confidence field in the audit, listed in
@@ -178,9 +184,14 @@ def classify_item(item: SortItem) -> None:
# faction group layer (GW naming, shared with warhammer_data)
def load_catalog(path: Path) -> list[dict]:
def load_catalog(path: Path,
dropped: frozenset[str] | None = DROPPED_SYSTEMS) -> list[dict]:
"""Load catalog entries, dropping non-collected game systems by default
(pass dropped=frozenset() to keep everything)."""
data = json.loads(path.read_text(encoding="utf-8"))
entries = data["entries"] if isinstance(data, dict) else data
if dropped:
entries = [e for e in entries if e.get("system") not in dropped]
for e in entries:
e["_clean"] = _clean_name(e["name"])
e["_clean_aliases"] = [_clean_name(a) for a in e.get("aliases", [])]
@@ -672,6 +683,17 @@ def _display_name(unit: str) -> str:
# plan / apply
# ---------------------------------------------------------------------------
def _drop_dropped_system(item: SortItem) -> None:
"""Dropped systems (see DROPPED_SYSTEMS) count as unclassified: the
collection does not cover them, so their names must not place files
into those sections. Called after keyword classification, before
matching - the item can still be re-homed by other signals."""
if item.system in DROPPED_SYSTEMS:
item.system = "Unknown"
item.group = item.faction = UNSORTED
item.subfaction = None
def sort_plan(
files: list[Path],
library: Path,
@@ -716,6 +738,7 @@ def sort_plan(
if unit_name is None:
item = inspect(f)
classify_item(item)
_drop_dropped_system(item)
match_catalog(item, catalog, threshold)
if root is not None:
# manual trees: let the real ancestor dir names compete
@@ -741,6 +764,7 @@ def sort_plan(
clean=_clean_name(unit_name), inner_names=listing,
)
classify_item(probe)
_drop_dropped_system(probe)
match_catalog(probe, catalog, threshold)
fields = {k: getattr(probe, k) for k in (
"system", "group", "faction", "subfaction", "klass",