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

164 lines
3.8 KiB
Markdown

# 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': <Team>, 'coeff': 45.5, 'countries': ['GER'], ...},
# {'id': 456, 'match': <QMatch>, '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)