From 20fd8c15f33602d538428593c317e97e75264f38 Mon Sep 17 00:00:00 2001 From: team3 Date: Thu, 23 Jul 2026 08:01:43 +0200 Subject: [PATCH] Kalibrierung: Holdout pro Match, Kanarienvogel ignoriert Summons/Sonder-Items, Baseline-Vergleich Co-Authored-By: Claude Fable 5 --- backend/tests/test_extract.py | 3 ++- backend/tft/cli.py | 12 ++++++++---- backend/tft/matches/extract.py | 21 +++++++++++++++++---- backend/tft/model/calibrate.py | 3 ++- backend/tft/model/learn.py | 2 +- 5 files changed, 30 insertions(+), 11 deletions(-) diff --git a/backend/tests/test_extract.py b/backend/tests/test_extract.py index 438d349..1bad3c8 100644 --- a/backend/tests/test_extract.py +++ b/backend/tests/test_extract.py @@ -31,6 +31,7 @@ FAKE_MATCH = { } STATIC = { + "meta": {"set": 17}, "units": {"TFT17_Rammus": {}}, "traits": {"TFT17_ResistTank": {}}, "items": {"TFT_Item_InfinityEdge": {}}, @@ -61,7 +62,7 @@ def test_validate_ids_clean(): def test_validate_ids_flags_unknown(): rows = extract_match(FAKE_MATCH) - static = {"units": {}, "traits": {}, "items": {}, "augments": {}} + static = {"meta": {"set": 17}, "units": {}, "traits": {}, "items": {}, "augments": {}} unknown = validate_ids(rows, static) assert unknown["unit:TFT17_Rammus"] == 8 assert unknown["item:TFT_Item_InfinityEdge"] == 8 diff --git a/backend/tft/cli.py b/backend/tft/cli.py index 4349877..33f2c84 100644 --- a/backend/tft/cli.py +++ b/backend/tft/cli.py @@ -82,13 +82,17 @@ def main() -> None: from tft import db from tft.model import artifact as artifact_mod from tft.model.calibrate import calibrate + from tft.staticdata.fetch import load_static conn = db.connect() - art = artifact_mod.load(current_set()) - result = calibrate(conn, art, holdout_only=True) + learned_art = artifact_mod.load(current_set()) + baseline_art = artifact_mod.build(load_static(), {}) + r_base = calibrate(conn, baseline_art, holdout_only=True) + r_learned = calibrate(conn, learned_art, holdout_only=True) conn.close() - print(f"holdout matches: {result['matches']}") - print(f"mean spearman (score vs placement): {result['mean_spearman']:.3f}") + print(f"holdout matches: {r_learned['matches']}") + print(f"spearman baseline: {r_base['mean_spearman']:.3f}") + print(f"spearman learned: {r_learned['mean_spearman']:.3f}") elif args.command == "autoplay": from tft.constants.loader import load_constants diff --git a/backend/tft/matches/extract.py b/backend/tft/matches/extract.py index 045840b..8c16822 100644 --- a/backend/tft/matches/extract.py +++ b/backend/tft/matches/extract.py @@ -39,16 +39,29 @@ def extract_match(match: dict) -> list[tuple]: return rows +BENIGN_UNIT_HINTS = ("minion", "summon", "pve", "follower") + + def validate_ids(rows: list[tuple], static: dict) -> Counter: - """Count ids in endboards that are unknown to the static data (patch-drift canary).""" + """Count ids in endboards that are unknown to the static data (patch-drift canary). + + Summons/PvE-Units und Sonder-Items (Radiant, Ornn, ...) sind erwartbar + unbekannt und werden ignoriert. + """ unknown: Counter = Counter() known_items = set(static["items"]) | set(static["augments"]) + set_prefix = f"TFT{static['meta']['set']}_" for row in rows: for u in json.loads(row[8]): - if u["character_id"] not in static["units"]: - unknown[f"unit:{u['character_id']}"] += 1 + api = u["character_id"] + if api not in static["units"]: + if not any(h in api.lower() for h in BENIGN_UNIT_HINTS): + unknown[f"unit:{api}"] += 1 for item in u.get("itemNames", []): - if item not in known_items: + if item in known_items: + continue + # Nur Items des aktuellen Sets oder generische zählen als Drift. + if item.startswith("TFT_Item_") or item.startswith(set_prefix): unknown[f"item:{item}"] += 1 for t in json.loads(row[9]): if t["name"] not in static["traits"]: diff --git a/backend/tft/model/calibrate.py b/backend/tft/model/calibrate.py index abd4655..924c37b 100644 --- a/backend/tft/model/calibrate.py +++ b/backend/tft/model/calibrate.py @@ -49,7 +49,8 @@ def calibrate(conn, artifact: dict, holdout_only: bool = False) -> dict: set_number = artifact["meta"]["set"] where = "WHERE set_number = ?" if holdout_only: - where += " AND rowid % 5 = 0" + # Holdout pro MATCH (nicht pro Board), sonst gibt es keine vollständigen 8er. + where += " AND substr(match_id, -1) IN ('0', '5')" rows = conn.execute( f"SELECT match_id, placement, units, augments FROM endboards {where}", (set_number,), diff --git a/backend/tft/model/learn.py b/backend/tft/model/learn.py index d79d5d2..2504cb0 100644 --- a/backend/tft/model/learn.py +++ b/backend/tft/model/learn.py @@ -80,7 +80,7 @@ def learn(rows: list[tuple]) -> dict: def learn_from_db(conn, set_number: int, exclude_holdout: bool = True) -> dict: where = "WHERE set_number = ?" if exclude_holdout: - where += " AND rowid % 5 != 0" + where += " AND substr(match_id, -1) NOT IN ('0', '5')" rows = conn.execute( f"SELECT placement, units, traits, augments FROM endboards {where}", (set_number,),