2026-02-20 22:08:04 +01:00

3.8 KiB

qualifiers-seeding

Automatisiert das Team-Seeding für UEFA-Qualifikationsturniere.

Trigger

  • Neue Saison erstellen
  • Team-Import
  • "seeding", "seed teams", "neue saison"

Kontext

Das Seeding erstellt die Node-Struktur und weist Teams basierend auf UEFA-Koeffizienten zu. Die Hauptfunktion ist seed_nodes25() in qualifiers/helpers.py.

Regeln

  1. Node-Struktur folgt UEFA-Format:

    Champions Path:
    └── UCL: Q1 → Q2 → Q3 → PO
    └── UEL: Q2 → Q3 → PO
    └── UECL: Q3 → PO
    
    League Path:
    └── UCL: Q2 → Q3 → PO
    └── UEL: Q3 → PO
    
  2. Koeffizienten-Ranking:

    # Formel: Koeffizient * 100000 - Position
    # Höherer Wert = besseres Ranking
    coefficients[team.id] = float(100000 * team.coefficient - team.position)
    
  3. State-Transitions:

    • none (0) → seeded (1): Nach seeded_teams.set()
    • Vorgänger-Nodes müssen mindestens State draw (3) haben
  4. GroupsOfSize automatisch erstellen:

    node.create_groupsofsize()
    # Erstellt: 4, 6, 8, 10, 12, 20 mit number=0
    

Beispiel: Nodes für neue Saison erstellen

from qualifiers.models import QPath, QTier, QStage, QNode

# Path erstellen
cp = QPath.objects.create(scenario=scenario, name='Champions Path')

# Tiers erstellen
ucl = QTier.objects.create(
    scenario=scenario,
    path=cp,
    name='UCL',
    ypos=3,
    optimize_prio=50
)

# Stages erstellen
q1 = QStage.objects.create(scenario=scenario, path=cp, name='Q1', xpos=1)
q2 = QStage.objects.create(scenario=scenario, path=cp, name='Q2', xpos=2)

# Nodes erstellen mit Navigation
node_q1 = QNode.objects.create(
    scenario=scenario,
    name='Q1',
    tier=ucl,
    stage=q1,
    type=0,  # Grouping
    state=1,  # Seeded
)

node_q2 = QNode.objects.create(
    scenario=scenario,
    name='Q2',
    tier=ucl,
    stage=q2,
    type=0,
    state=0,  # Wartet auf Q1
)

# Navigation setzen
node_q1.winners = node_q2
node_q1.save()

Beispiel: Teams seeden

from scheduler.models import Team
from qualifiers.models import QNode

node = QNode.objects.get(
    scenario=scenario,
    tier__name='UCL',
    stage__name='Q1'
)

# Top 16 Teams nach Koeffizient
teams = Team.objects.filter(
    season=scenario.season,
    active=True
).order_by('-coefficient')[:16]

# Teams zuweisen
node.seeded_teams.set(teams)
node.state = 1  # Seeded
node.save()

# Gruppengrößen erstellen
node.create_groupsofsize()

Beispiel: Upcomers von Vorgänger-Node

# Nach Q1 Draw: Teams für Q2 sammeln
q1_node = QNode.objects.get(scenario=scenario, tier__name='UCL', stage__name='Q1')
q2_node = QNode.objects.get(scenario=scenario, tier__name='UCL', stage__name='Q2')

# Upcomers sind finalisierte Matches aus Q1
upcomers = q2_node.upcomers(scenario)

# Format:
# [
#     {'id': 123, 'team': <Team>, 'coeff': 45.5, 'countries': ['GER'], ...},
#     {'id': 456, 'match': <QMatch>, 'coeff': 30.2, 'countries': ['ESP', 'ITA'], ...},
# ]

Validierung

def validate_node_seeding(node):
    """Prüft Seeding-Voraussetzungen."""
    errors = []

    # Mindestens 2 Teams
    total_teams = node.nTeams(node.upcomers(node.scenario))
    if total_teams < 2:
        errors.append("Mindestens 2 Teams erforderlich")

    # Gerade Anzahl
    if total_teams % 2 != 0:
        errors.append("Ungerade Teamanzahl")

    # Alle Vorgänger finalisiert
    for pred in node.qnode_winners.all() | node.qnode_losers.all():
        if pred.current_state(node.scenario) < 3:
            errors.append(f"Vorgänger {pred.which()} nicht im Draw-State")

    return errors

Referenz-Dateien