# 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](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**: ```python # 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**: ```python node.create_groupsofsize() # Erstellt: 4, 6, 8, 10, 12, 20 mit number=0 ``` ## Beispiel: Nodes für neue Saison erstellen ```python 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 ```python 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 ```python # 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': , 'coeff': 45.5, 'countries': ['GER'], ...}, # {'id': 456, 'match': , 'coeff': 30.2, 'countries': ['ESP', 'ITA'], ...}, # ] ``` ## Validierung ```python 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 - [qualifiers/helpers.py](qualifiers/helpers.py) - seed_nodes25() - [qualifiers/uefadigitalapi/seed_2025.py](qualifiers/uefadigitalapi/seed_2025.py) - [docs/qualifiers/workflows/seeding.md](docs/qualifiers/workflows/seeding.md)