Files
tft/backend/tests/test_pairs.py
2026-09-07 07:44:09 +02:00

114 lines
4.1 KiB
Python

"""Gruppen: Bindung aus den Pro-Comps, Stärke aus den Boards."""
from tft.model import pairs
from tft.staticdata import procomps
UNITS = {a: {"cost": 1, "traits": ["T"], "asset_names": [f"DA_{a}"]}
for a in "ABCDEF"}
def board(place, *units):
return {"place": place, "cell": (9, 3),
"entities": [(u, 2, 0) for u in units]}
def cell(b):
return b["cell"]
def test_bond_asks_about_the_rarer_unit():
"""Nicht "wie oft zusammen", sondern "wie oft zusammen, wenn möglich".
B steht nie ohne A. C steht halb so oft bei A. Rohe Zählung sähe C
vorne, weil C öfter vorkommt.
"""
groups = [["A", "B", "X", "Y"]] * 3 + [["A", "C", "X", "Y"]] * 2 \
+ [["C", "D", "X", "Y"]] * 2
w = pairs.weights(groups, pseudo=0)
assert w[("A", "B")] == 1.0
assert w[("A", "C")] == 0.5
def test_pseudo_counts_hold_back_single_sightings():
"""Ein Paar mit einer Nennung darf nicht wie eine Regel aussehen."""
groups = [["A", "B", "C", "D"]]
assert pairs.weights(groups, pseudo=0)[("A", "B")] == 1.0
assert pairs.weights(groups, pseudo=3)[("A", "B")] == 0.25
def test_every_pair_in_a_group_must_clear_the_threshold():
"""Ein Dritter darf nicht an nur einem der beiden hängen.
C bindet stark an A, aber gar nicht an B. Also kein Trio.
"""
bond = {("A", "B"): 0.9, ("A", "C"): 0.9, ("B", "C"): 0.1}
out = pairs.candidates(bond, UNITS, min_bond=0.4)
assert ("A", "B") in out and ("A", "C") in out
assert ("A", "B", "C") not in out
def test_a_group_needs_a_shared_trait():
"""Ohne das kommt Unsinn heraus: eine Comp hat acht bis neun Units,
zwei davon stehen nebeneinander, ohne etwas miteinander zu tun.
"""
units = {"A": {"traits": ["T"]}, "B": {"traits": ["T"]},
"C": {"traits": ["U"]}}
bond = {("A", "B"): 0.9, ("A", "C"): 0.9, ("B", "C"): 0.9}
out = pairs.candidates(bond, units, min_bond=0.4)
assert ("A", "B") in out
assert ("A", "C") not in out and ("A", "B", "C") not in out
def test_strength_is_measured_against_comparable_boards():
boards = [board(1, "A", "B", "C") for _ in range(400)] \
+ [board(7, "D", "E", "F") for _ in range(400)]
out = pairs.strength(boards, {(9, 3): 4.0}, cell,
[("A", "B"), ("D", "E")], min_boards=10)
assert out[("A", "B")]["edge"] > 0 > out[("D", "E")]["edge"]
assert out[("A", "B")]["boards"] == 400
def test_rare_groups_are_dropped():
boards = [board(1, "A", "B") for _ in range(5)]
assert pairs.strength(boards, {(9, 3): 4.0}, cell, [("A", "B")],
min_boards=10) == {}
def test_select_drops_overlapping_and_negative_groups():
"""Sonst steht dieselbe Gruppe zwanzigmal leicht abgewandelt da."""
scored = {("A", "B", "C"): {"edge": 0.5}, ("A", "B"): {"edge": 0.4},
("D", "E"): {"edge": 0.2}, ("A", "F"): {"edge": -0.1}}
assert pairs.select(scored) == [("A", "B", "C"), ("D", "E")]
def test_build_is_empty_without_input():
assert pairs.build([], [board(1, "A", "B")], {}, cell, UNITS) == {}
assert pairs.build([["A", "B", "C", "D"]], [], {}, cell, UNITS) == {}
def test_comps_reads_every_stage():
"""Level 4 bis 9 sind eigene Stufen — jede zählt als Beobachtung."""
doc = {
"content": {"comps": [{"data": {"$ref": "#/comp/x"}, "tier": "S"}]},
"refs": [{
"content_id": "x",
"metadata": {"title": "Test"},
"content": {
"unit_positions": {str(i): {"apiName": f"DA_{a}"}
for i, a in enumerate("ABCD")},
"variations": [
{"type": "comp",
"unit_positions": {str(i): {"apiName": f"DA_{a}"}
for i, a in enumerate("ABCDE")}},
{"type": "tierlist"},
# zu klein, fliegt raus
{"type": "comp",
"unit_positions": {"0": {"apiName": "DA_A"}}},
],
},
}],
}
out = procomps.comps(doc, UNITS)
assert [len(c["units"]) for c in out] == [4, 5]
assert all(c["tier"] == "S" for c in out)