Document and implement CSN-C-NEU mandatory division rules: Add TURNIER_KLASSEN.md detailing forced divisions by license categories; establish uniform labels and keys (e.g., LZF_ONLY and R1_PLUS); draft optional youth division rules. Extend validation in Validierungsregeln.md. Implement FEI-ID resolver and mapping endpoint in Masterdata service.

This commit is contained in:
2026-04-02 18:36:59 +02:00
parent bbe5b1a357
commit 4ae701d969
8 changed files with 540 additions and 25 deletions
@@ -0,0 +1,181 @@
---
type: RULE_SPEC
status: DRAFT
owner: Rulebook Expert
last_update: 2026-04-02
---
# Turnier-Klassen und Abteilungs-Zwangsteilung (ÖTO)
Ziel: Einheitliche, maschinenlesbare Spezifikation der Klassensystematik und der verpflichtenden Abteilungs-Teilungsregeln ("Zwangsteilung") gemäß ÖTO. Diese Regeln steuern Backend-Validierungen, Frontend-UX (Hinweise/Warnungen) und Exportlogik.
Quellen/Verweise:
- Roadmap: `docs/04_Agents/Roadmaps/Rulebook_Roadmap.md` (A-2)
- DomänenModell: `docs/03_Domain/01_Core_Model/Domain_Model_Veranstaltung_Turnier_Bewerb_Abteilung.md`
- Validierungsregeln (Lizenz/Alter): `docs/03_Domain/02_Reference/Validierungsregeln.md`
- ÖTO Referenzstellen (ParagraphenPins zur Nachverfolgung):
- Springen: ÖTO 2026, Kapitel „Springen“, § 231 Abs. 13 (CSNCNEU Teilungsregeln) [PIN: OETO-SPR-231]
- Dressur: ÖTO 2026, Kapitel „Dressur“, § 103 Abs. 25 (Teilnahme/Leistungsstufen) [PIN: OETO-DRS-103]
- Vielseitigkeit: ÖTO 2026, Kapitel „Vielseitigkeit“, §§ 3xx (Teilnahme/Abteilungen) [PIN: OETO-VS-3XX]
Hinweis Rechtslage: Die obigen „ParagraphenPins“ verankern die Stellen im ÖTO. Exakte Absatz-/Ziffernangaben werden nach juristischer Finalisierung ergänzt. Inhaltliche Logik entspricht dem Stand der Praxis (CSNCNEU) und wird bei Abweichungen angepasst.
---
## 1. Begriffe (Auszug)
- Bewerb: Sportliche Ausschreibungseinheit innerhalb eines Turniers (z. B. Springen 95 cm, Stilspringen). Enthält 1..N Abteilungen.
- Abteilung: Startgruppe innerhalb eines Bewerbs. Kann organisatorische oder regelbedingte Gründe haben (Zwangsteilung, Lizenz, Jugend usw.).
- Zwangsteilung: Verpflichtende Abteilungsbildung anhand lizenz-/leistungsbezogener Kriterien gemäß ÖTO.
---
## 2. CSN (Springen national) — Zwangsteilung CNEU
Gültig für Bewerbe der Kategorie „CSNCNEU“.
### 2.1 Regelübersicht (Zwangsteilung)
- Rechtsgrundlage: ÖTO § 231 (vgl. [PIN: OETO-SPR-231])
- Bewerbe mit Höhe ≤ 95 cm:
- Abteilung A: Label: „ohne Lizenz“ — Key: `LZF_ONLY` — Allowed: `LZF`
- Abteilung B: Label: „mit Lizenz“ — Key: `R1_PLUS` — Allowed: `R1|R2|R3|R4`
- Bewerbe mit Höhe ≥ 100 cm:
- Abteilung A: Label: „R1“ — Key: `R1_ONLY` — Allowed: `R1`
- Abteilung B: Label: „R2 und höher“ — Key: `R2_PLUS` — Allowed: `R2|R3|R4`
Erläuterungen:
- Die Abteilungsbezeichnungen dienen dem Frontend (Label) und der Preisgeld-/Siegerehrungslogik. Technisch werden die Abteilungen per Attribut „LizenzGruppe“ markiert.
- Veranstalter dürfen enger teilen (z. B. zusätzliche Jugendabteilungen), nicht jedoch lockern (PflichtZweiteilung muss bestehen bleiben).
### 2.2 Maschinenlesbare Spezifikation
Tabelle: CSNCNEU Zwangsteilung nach Höhe
| Höhe (cm) | Abteilung 1 (Label · Key) | Abteilung 2 (Label · Key) | Bemerkung |
|---|---|---|---|
| ≤ 95 | „ohne Lizenz“ · `LZF_ONLY` | „mit Lizenz“ · `R1_PLUS` | `R1_PLUS` umfasst `R1|R2|R3|R4` |
| ≥ 100 | „R1“ · `R1_ONLY` | „R2 und höher“ · `R2_PLUS` | `R2_PLUS` umfasst `R2|R3|R4` |
Pseudocode (Ableitung der PflichtAbteilungen):
```kotlin
data class ForcedDivision(val label: String, val allowedLicenses: Set<String>)
fun forcedDivisionsCsnCNeu(heightCm: Int): List<ForcedDivision> =
if (heightCm <= 95) listOf(
ForcedDivision(label = DivisionLabels.OHNE_LIZENZ, allowedLicenses = setOf("LZF")),
ForcedDivision(label = DivisionLabels.MIT_LIZENZ, allowedLicenses = setOf("R1","R2","R3","R4"))
) else listOf(
ForcedDivision(label = DivisionLabels.R1, allowedLicenses = setOf("R1")),
ForcedDivision(label = DivisionLabels.R2_UND_HOEHER, allowedLicenses = setOf("R2","R3","R4"))
)
```
Validierung (vereinfachte Regel):
- Wenn Bewerbskategorie = `CSN-C-NEU`, dann müssen genau zwei Abteilungen gemäß obiger Ableitung vorhanden sein. Jede Nennung muss in einer Abteilung landen, deren `allowedLicenses` die Lizenz des Reiters enthält.
Fehlermeldungen (UX):
- „Für CSNCNEU Bewerbe ist eine Zwangsteilung nach Lizenz vorgeschrieben (ÖTO § 231). Bitte beide Abteilungen anlegen.“
- „Die Abteilung R2 und höher darf nur Lizenzen R2/R3/R4 enthalten.“
- Hinweistext (Quelle): „Rechtsgrundlage: ÖTO § 231 (CSNCNEU).“
---
## 3. CDN (Dressur national) — Prüfung weiterer Zwangsteilungen
Status: geprüft. Nach aktuellem Stand bestehen in den Einsteiger/NiedrigKlassen keine zwingenden LizenzZwangsteilungen analog zu CSNCNEU. Übliche Praxis ist die optionale Teilung nach Leistungsklassen/Jahrgängen (z. B. Jugendliche), jedoch keine verpflichtende Zweiteilung „ohne/mit Lizenz“.
- Ergebnis: Keine allgemeine, disziplinweite Zwangsteilung identifiziert. Veranstalter können freiwillig teilen (z. B. RD1 vs. RD2+), sofern ÖTO konform. Bezug: ÖTO § 103 (vgl. [PIN: OETO-DRS-103]).
Folgeaktion: Bei Veröffentlichung der finalen DressurAbschnitte erneut prüfen. Bis dahin: Keine systemweite Pflichtregel hinterlegen.
---
## 4. CCN (Vielseitigkeit national) — Prüfung weiterer Zwangsteilungen
Status: vorläufig geprüft. In den nationalen Vielseitigkeitsklassen (CCN) ist keine generische Zwangsteilung nach Lizenzgruppen („ohne/mit“ bzw. `R1` vs. `R2+`) als Pflicht verankert. Teilungen erfolgen eher nach Leistungsniveau, Altersklassen oder organisatorischen Gründen.
- Ergebnis: Keine disziplinweiten PflichtTeilungsregeln identifiziert. Konkrete Ausnahmen sind turnierspezifisch. Bezug: ÖTO Kapitel „Vielseitigkeit“, §§ 3xx (vgl. [PIN: OETO-VS-3XX]); exakte Absätze folgen nach Finalisierung.
---
## 5. Implementierungshinweise (Backend/Frontend)
- Backend:
- Regel „CSNCNEU → PflichtAbteilungen“ als RegulationasData hinterlegen (z. B. `reg_forced_divisions` mit Feldern: `category`, `height_threshold`, `division_key`, `allowed_licenses`).
- Serverseitige Validierung beim Anlegen/Bearbeiten eines CSNCNEU Bewerbs: genau zwei Abteilungen erzwingen, Labels/AllowedSets prüfen.
- Nennvalidierung: Lizenz des Reiters ∈ `allowedLicenses` der Zielabteilung.
- Frontend:
- Wizard/Editor legt bei CSNCNEU automatisch beide Abteilungen an (konfigurierbare Labels).
- LiveHinweis, wenn eine Abteilung fehlt oder falsche Lizenzen zugeordnet sind.
---
## 6. Einheitliche LabelKonventionen für Abteilungen
Ziel: Einheitliche, i18nfähige Benennung in UI, Exporten und Validierung. Deutsche StandardLabels und technische Keys:
- DivisionLabels (Deutsch):
- OHNE_LIZENZ → „ohne Lizenz“ (Key: `LZF_ONLY`)
- MIT_LIZENZ → „mit Lizenz“ (Key: `R1_PLUS`)
- R1 → „R1“ (Key: `R1_ONLY`)
- R2_UND_HOEHER → „R2 und höher“ (Key: `R2_PLUS`)
Richtlinien:
- Labels in UI exakt wie oben; keine Varianten („R2+“ nur in Klammern/Hinweisen, offizielles Label: „R2 und höher“).
- Keys sind stabil und werden in Datenpersistenz/Exports verwendet. Übersetzungen erfolgen per i18n.
Pseudocode (Konstanten):
```kotlin
object DivisionLabels {
const val OHNE_LIZENZ = "ohne Lizenz" // key: LZF_ONLY
const val MIT_LIZENZ = "mit Lizenz" // key: R1_PLUS
const val R1 = "R1" // key: R1_ONLY
const val R2_UND_HOEHER = "R2 und höher" // key: R2_PLUS
}
```
---
## 7. Erweiterungen: Jugend/Jahrgangsteilungen (optional)
Status: Optionales RegelSet, kein ÖTOPflichtumfang wie bei CSNCNEU. Veranstalter können zusätzlich nach Jahrgängen/Jugendklassen teilen, sofern ÖTOkonform (vgl. Dressur § 103 und disziplinspezifische Jugendbestimmungen).
Modellierung als optionale Regeln:
- Datenmodell (Beispiel als RegulationasData):
- Tabelle `reg_optional_divisions`:
- `category` (z. B. `CSN`, `CDN`)
- `discipline` (SPRINGEN, DRESSUR, VIELSEITIGKEIT)
- `division_key` (z. B. `U16`, `U18`, `U25`, `AMATEURE`)
- `label` (z. B. „Jugend U16“, „Jugend U18“)
- `age_range` (z. B. `14-16` Jahre, berechnet gem. Stichtag 1.1.; vgl. Validierungsregeln § „Altersklassen Pferd/Reiter“)
- `license_filter` (optional, Menge erlaubter Lizenzen)
- `notes` (Freitext/ParagraphenBezug)
Beispiel (Pseudocode):
```kotlin
data class OptionalDivisionRule(
val category: String, // CSN, CDN
val discipline: String, // SPRINGEN, DRESSUR
val divisionKey: String, // U16, U18
val label: String, // "Jugend U16"
val ageFrom: Int, val ageTo: Int?, // inklusiv, To=null = open ended
val allowedLicenses: Set<String>? = null // null = alle
)
fun applies(rule: OptionalDivisionRule, athleteAge: Int, license: String): Boolean =
(athleteAge >= rule.ageFrom) && (rule.ageTo?.let { athleteAge <= it } ?: true) &&
(rule.allowedLicenses?.contains(license) ?: true)
```
UXHinweistexte:
- „Optionale Jugendabteilung aktiv: Nur Athlet:innen des Jahrgangsbereichs {label} werden hier gewertet.“
- „Diese Abteilung ist optional; PflichtZwangsteilung (falls vorhanden) bleibt unberührt.“
---
## 8. Offene Punkte / ToDos
- Juristische Finalisierung: Exakte Absatz-/Ziffernangaben zu [PIN: OETO-SPR-231], [PIN: OETO-DRS-103], [PIN: OETO-VS-3XX] nachtragen.
- BackendSeed: `reg_forced_divisions` und `reg_optional_divisions` befüllen; Keys/Labels gemäß Abschnitt 6 verwenden.
- FE/UX: i18nMapping für DivisionLabels bereitstellen; EditorPresets für CSNCNEU und optionale Jugendabteilungen.