---
type: RULE_SPEC
status: DRAFT
owner: Rulebook Expert
last_update: 2026-04-03
---

# Validierungsregeln (ÖTO/FEI)

Ziel: Dieses Dokument definiert präzise, testbare Validierungsregeln als Grundlage für Frontend (Live‑Validation), Backend (serverseitige Validation) und QA (Testfälle). Änderungen erfolgen versioniert und mit Beispielen.

Quellen/Verweise:
- Roadmap: `docs/04_Agents/Roadmaps/Rulebook_Roadmap.md`
- Domänen‑Modell: `docs/03_Domain/01_Core_Model/Domain_Model_Veranstaltung_Turnier_Bewerb_Abteilung.md`

---

## 1. OEPS‑Mitgliedsnummer (Austria)

Status: Draft – finale Bestätigung durch OEPS/Verbandsdokumente ausstehend. Regel ist bewusst konservativ und maschinenlesbar formuliert.

### 1.1 Zweck
- Eindeutige Identifikation von Reiter:innen/Vereinsmitgliedern des OEPS in Formularen, Nennungen und Sync‑Schnittstellen.

### 1.2 Kanonisches Format (Normalform)
- Zeichenraum: Großbuchstaben A–Z, Ziffern 0–9, Bindestrich `-`.
- Erlaubte Schreibweisen (Regex in PCRE‑Notation):
  1) Mit Präfix: `^OEPS-[0-9]{6,8}$`
  2) Ohne Präfix: `^[0-9]{6,8}$`

Erläuterungen:
- Länge der numerischen Komponente: 6 bis 8 Ziffern (Spielraum für Altdaten/neuere Nummernkreise). Engführen auf exakt 7 Ziffern ist möglich, sobald offiziell bestätigt.
- Präfix ist optional in der Eingabe, wird bei Speicherung normalisiert (siehe 1.5).

### 1.3 Verbote / Nicht erlaubt
- Leerzeichen innerhalb der Nummer (z. B. `123 4567`).
- Punkte, Schrägstriche, Unterstriche oder andere Sonderzeichen (`. / _ + * ? ! …`).
- Alphanumerische Suffixe oder Buchstaben in der Nummer (z. B. `123456A`).
- Falsches oder gemischtes Präfix (z. B. `OEPS:` oder `Oeps-`).
- Führende Nullen sind erlaubt, zählen jedoch zur Gesamtlänge (z. B. `00123456`).

### 1.4 Beispiele
- Gültig:
  - `123456`
  - `7654321`
  - `00123456`
  - `OEPS-1234567`
  - `OEPS-00123456`

- Ungültig (mit Begründung):
  - `12345` — zu kurz (min. 6 Ziffern)
  - `123456789` — zu lang (max. 8 Ziffern)
  - `12A4567` — Buchstaben in der Nummer nicht erlaubt
  - `OEPS1234567` — fehlender Bindestrich nach Präfix
  - `OEPS-12 34567` — Leerzeichen nicht erlaubt
  - `oeps-1234567` — falsche Groß/Kleinschreibung im Präfix (nur `OEPS-` zulässig)
  - `OEPS-1234.567` — Punkt nicht erlaubt

### 1.5 Normalisierung (Speicherformat)
- Interne Normalform: `NNNNNNNN` (nur Ziffern, links mit Nullen auf 8 Zeichen aufgefüllt), sofern Nummernlänge ≤ 8.
- Eingaben mit Präfix werden gespeichert ohne Präfix, jedoch mit Metadatum `source_prefix = OEPS`.
- Beispiel: `OEPS-1234567` → `01234567` (Normalform), `source_prefix=OEPS`.

Hinweise:
- Falls sich offiziell herausstellt, dass die Länge fix `7` ist, wird die Normalisierung entsprechend angepasst (kein linksseitiges Auffüllen über 7 hinaus). Diese Änderung wäre eine MINOR Schema/Validation‑Version.

### 1.6 Pseudocode‑Validierung
```kotlin
fun validateOepsId(input: String): Boolean {
  val trimmed = input.trim()
  val withPrefix = Regex("^OEPS-[0-9]{6,8}$")
  val plain = Regex("^[0-9]{6,8}$")
  return withPrefix.matches(trimmed) || plain.matches(trimmed)
}
```

### 1.7 Fehlermeldungen (UX‑Texte)
- Kurz: "Ungültige OEPS‑Mitgliedsnummer. Erlaubt sind 6–8 Ziffern, optional mit Präfix 'OEPS-'."
- Lang: "Bitte eine gültige OEPS‑Mitgliedsnummer eingeben: 6–8 Ziffern (z. B. 1234567 oder OEPS-1234567). Keine Leerzeichen oder Sonderzeichen."

### 1.8 Offene Punkte / ToDo
- Verbandsbestätigung: Fixe Länge (6, 7 oder 8) und eventuelle Prüfziffer klären.
- Mapping Altsysteme: Enthalten historische Kartenformat‑Varianten? Falls ja, eigene Legacy‑Regex aufnehmen.
- QA: Testfälle für Grenzwerte (6/8 Ziffern), Präfix‑Varianten, Whitespace‑Trimmung.

---

## 2. FEI‑ID
Status: Draft – auf Basis FEI General Regulations (Art. 113–114) und vorhandener Systemdaten. Endgültige Bestätigung via FEI Lookup/API steht aus.

### 2.1 Zweck
- Eindeutige Identifikation international registrierter Athlet:innen und Pferde bei FEI‑relevanten Bewerben/Kategorien (CI/CIO/CSI/CDI/CCI etc.).

### 2.2 Gültige Formate (Eingabe)
- Primär (numerisch, aktuell üblich):
  - Regex: `^[0-9]{7,8}$`  
    Erläuterung: 7–8‑stellige numerische FEI‑IDs (z. B. `10011469`).
- Legacy/Referenzcode (in Legacy‑Daten sichtbar):
  - Regex: `^[0-9]{3}[A-Z]{2}[0-9]{2}$`  
    Beispiel: `104FE22`. Diese Codes werden akzeptiert, aber bei Speicherung nach Möglichkeit gegen die numerische FEI‑ID aufgelöst (siehe 2.5).

Nicht erlaubt:
- Leerzeichen, Trennzeichen, gemischte Schreibweisen mit Präfixen (z. B. `FEI-10011469`), alphanumerische Mischformen außerhalb des obigen Legacy‑Musters.

### 2.3 Pflichtfelder‑Regel (Wann ist FEI‑ID erforderlich?)
- International (FEI‑Events: CI/CSI/CDI/CCI/CIO/CH/…):
  - Athlet: FEI‑ID Pflicht.
  - Pferd: FEI‑ID Pflicht (inkl. FEI‑Pass/Microchip gem. FEI‑Regeln, vgl. Art. 114, 137 FEI GR).
- National (ÖTO‑Events: CN/CSN/CDN/CCN):
  - Athlet: FEI‑ID optional (nur wenn FEI‑registriert).  
  - Pferd: FEI‑ID optional (nur wenn FEI‑registriert).  
  - Ausnahme: Wenn eine nationale Prüfung als FEI‑qualifikationsrelevant ausgewiesen ist, kann FEI‑ID für Datenexporte empfohlen/erforderlich sein (Veranstalterhinweis).

Hinweis: Die konkrete Pflicht koppeln wir im System an das Feld „Turnierkategorie“ und Disziplin, konfigurierbar per Regel‑Set.

### 2.4 Beispiele
- Gültig: `10011469`, `10019075`, `10028445`, `104FE22` (Legacy).
- Ungültig: `FEI10011469` (Präfix), `10011 469` (Leerzeichen), `10A11469` (Buchstabe in numerischem Format), `104F-E22` (Sonderzeichen).

### 2.5 Normalisierung (Speicherformat)
- Bevorzugtes Speicherformat: numerische FEI‑ID (`[0-9]{7,8}`) als String ohne Trennzeichen.
- Legacy‑Referenzcode wird – sofern möglich – vor Speicherung via Mapping/Lookup in numerische FEI‑ID überführt. Falls kein Mapping möglich, speichern als eingegeben plus `source_format = LEGACY_CODE`.

### 2.6 Pseudocode‑Validierung
```kotlin
fun validateFeiId(input: String): Boolean {
  val s = input.trim().uppercase()
  val numeric = Regex("^[0-9]{7,8}$")
  val legacy = Regex("^[0-9]{3}[A-Z]{2}[0-9]{2}$")
  return numeric.matches(s) || legacy.matches(s)
}
```

### 2.7 Fehlermeldungen (UX‑Texte)
- Kurz: "Ungültige FEI‑ID. Erlaubt sind 7–8 Ziffern (z. B. 10011469)."
- Lang: "Bitte eine gültige FEI‑ID eingeben: 7–8 Ziffern (z. B. 10011469). Historische Referenzcodes (z. B. 104FE22) werden akzeptiert und – wenn möglich – automatisch aufgelöst."

### 2.8 Quellen/Verweise
- FEI General Regulations, insbesondere Art. 113 (Registration and Eligibility) und Art. 114 (Horse Identification) — `docs/03_Domain/02_Reference/FEI_Regelwerk/FEI-2026_General-Regulations_…md`
- Systembeispiele/Fixtures in Frontend‑Stores (FEI‑IDs): `frontend/shells/meldestelle-desktop/.../Stores.kt`

### 2.9 Backend‑Lookup (Masterdata‑SCS)
- Endpoint: `GET /api/fei/resolve/{id}`
  - Eingabe: `{id}` numerisch (`^[0-9]{7,8}$`) oder Legacy‑Code (`^[0-9]{3}[A-Z]{2}[0-9]{2}$`).
  - Erfolg 200: `{ input, normalizedNumericId, sourceFormat, wasMapped, found: true }`
  - Nicht gefunden 404: `{ input, normalizedNumericId: null, sourceFormat: null, wasMapped: false, found: false }`
- Mapping‑Quelle: `backend/services/masterdata/masterdata-service/src/main/resources/data/fei-id-mapping.json` (kann später aus DB gespeist werden).

---

## 3. Lizenzklassen (R1–R4, RD1–RD3, LIZENZFREI)
Status: Draft – basierend auf ÖTO‑Praxis und ZNS‑Lizenzdaten. Detaillierte Paragraphen‑Zitate werden nachgereicht (A‑2/A‑3 Arbeiten verknüpft).

### 3.1 Katalog gültiger Lizenzklassen
- Reiten Springen (R‑Klassen): `R1`, `R2`, `R3`, `R4`
- Dressur Reiten (RD‑Klassen): `RD1`, `RD2`, `RD3`
- Lizenzfrei/ohne Lizenz Kennzeichnung: `LIZENZFREI` (Enum-Key in `LizenzKlasseE`; Label: „ohne Lizenz“)

Erweiterbarkeit: Weitere Spezial‑/Jugend‑ oder Fahrer‑Lizenzen können ergänzt werden, sobald in ÖTO/ZNS erforderlich.

### 3.2 Grundregeln der Zuordnung (vereinfachte Erstfassung)
- Springen (CSN):
  - Bewerbe bis inkl. 95 cm: Teilnahme mit `LIZENZFREI` (Abt. „ohne Lizenz“) oder `R1` (Abt. „mit Lizenz`).
  - Ab 100 cm: mindestens `R1` erforderlich; ab bestimmten Höhen empfohlen/erforderlich `R2+` (veranstalter‑/ausschreibungsabhängig).  
  - Zwangsteilungsregeln siehe Roadmap A‑2 (eigener Abschnitt).
- Dressur (CDN):
  - Einsteigerprüfungen (z. B. Dressurreiterprüfungen niedrig): `LIZENZFREI` oder `RD1`.
  - Ab definiertem Schwierigkeitsgrad: `RD1+`, höhere Klassen `RD2/RD3` gemäß Ausschreibung.

Hinweis: Die vollständige Matrix „Lizenzklasse × Bewerbsklasse“ ist in
`docs/03_Domain/02_Reference/OETO_Regelwerk/B2-Backend-Uebergabe-Regulation-as-Data.md` (Abschnitte 4 und 5)
spezifiziert und als Flyway V009 im Backend hinterlegt. Nach Fachfreigabe wird der Status auf STABLE angehoben.

### 3.3 Validierungslogik (Platzhalter bis zur finalen Matrix)

- Eingabe muss in obiger Katalogliste vorkommen (`R1|R2|R3|R4|RD1|RD2|RD3|LIZENZFREI`).
- Bei Auswahl eines Bewerbs wird die erlaubte(n) Lizenzklasse(n) aus der Disziplin/Höhe/Schwierigkeit abgeleitet.
- Fehler, wenn gewählte Lizenzklasse nicht in der erlaubten Menge liegt.

Pseudocode (vereinfacht):
```kotlin
fun isLicenseAllowed(discipline: Discipline, heightCm: Int?, testLevel: DressageLevel?, license: String): Boolean {
  val allowed = allowedLicensesFor(discipline, heightCm, testLevel) // Tabelle/Regel-Engine
  return license in allowed
}
```

### 3.4 Fehlermeldungen (UX‑Texte)
- Kurz: "Diese Lizenzklasse ist für den ausgewählten Bewerb nicht zugelassen."
- Lang: "Bitte eine für diesen Bewerb zugelassene Lizenz auswählen. Die Zulassung richtet sich nach Disziplin und Höhe/Schwierigkeitsgrad (ÖTO)."

### 3.5 Quellen/Verweise
- ÖTO (Abschnitte zu Lizenzen, Springen/Dressur Teilnahmevoraussetzungen)
- ZNS‑Lizenzdaten: `docs/OePS/ZNS/LIZENZ01.dat` (Datenquelle, strukturierter Export) – Parsing/Anlage in Masterdata‑SCS.
- Teilungs-/Warnlogik: `docs/03_Domain/02_Reference/OETO_Regelwerk/Warn-Logik-Spezifikation-competition-context.md`

### 3.6 Lizenz‑Zuordnungstabelle (DRAFT, final mit Paragraphen‑Verweisen)
- Springen (CSN) — Bezug ÖTO § 231 ff. (finale Paragraphennummern nachreichen):

| Höhe (cm) | Zulässige Lizenz-Abteilungen | Primär-Bezug ÖTO                 |
|-----------|------------------------------|----------------------------------|
| ≤ 95      | LIZENZFREI „ohne Lizenz“     | § 231 (Zwangsteilung Einsteiger) |
| ≤ 95      | R1 „mit Lizenz“              | § 231                            |
| 100       | R1+                          | § 231                            |
| 105–110   | R1, R2+ (Empf. R2)           | § 231                            |
| 115–120   | R2+                          | § 231                            |
| 125–135   | R3+                          | § 231                            |
| ≥ 140     | R4                           | § 231                            |

- Dressur (CDN) — Bezug ÖTO § 103 ff. (finale Paragraphennummern nachreichen):

| Prüfungsniveau (national, äquiv.)  | Zulässige Lizenzen | Primär-Bezug ÖTO |
|------------------------------------|--------------------|------------------|
| Einsteiger/Dressurreiter (niedrig) | LIZENZFREI, RD1    | § 103            |
| A/L                                | RD1+               | § 103            |
| LM/M                               | RD2+               | § 103            |
| S                                  | RD3                | § 103            |

Hinweise:
- Veranstalter/Ausschreibung kann engere Anforderungen definieren, jedoch nicht lockern.
- Zwangsteilungsregeln für CSN‑C‑NEU sind in A‑2 separat spezifiziert und ergänzen die obige Tabelle.

---

## 4. Altersklassen Pferd
Status: Draft – FEI/ÖTO konsolidiert; Detailtabellen pro Disziplin werden ergänzt.

### 4.1 Stichtagsregel (Altersberechnung)
- Das Pferdealter wird für das gesamte Kalenderjahr mit Stichtag 1. Jänner bestimmt (Jahrgangsregel).  
  Beispiel: Geburtsdatum 15.06.2020 → Alter 2026 = 6 (ab 01.01.2026).

Pseudocode:
```kotlin
fun horseAgeOnJan1(birthYear: Int, year: Int): Int = year - birthYear
```

### 4.2 Mindestalter – Grundregeln (Erstfassung, Disziplin-übergreifend)
- National (ÖTO, typische Praxis):
  - Springen bis 100 cm: min. 4 Jahre
  - Springen > 100 cm bis 120 cm: min. 5 Jahre
  - Springen > 120 cm: min. 6 Jahre (Empfehlung/abhängig von Klasse)
  - Dressur Einstieg/leichte Prüfungen: min. 4 Jahre
  - Dressur höhere Klassen (z. B. L/M/S‑ähnlich): min. 5–6 Jahre (konkret per Tabelle nachzureichen)
- International (FEI, vgl. Art. 136 GR):
  - Disziplinspezifische Mindestalter (werden tabellarisch hinterlegt; Abhängig von Disziplin/Testlevel/Star‑Rating).

Hinweis: Konkrete, rechtssichere Tabellen (Disziplin × Klasse/Höhe × Mindestalter) werden nach Paragraphen‑Sichtung ergänzt und in Masterdata‑SCS versioniert.

### 4.3 Validierungslogik
- Errechne `age = horseAgeOnJan1(geburtsjahr, veranstaltungsjahr)`.
- Prüfe `age >= minAgeFor(discipline, heightCm?, testLevel?)` laut Matrix.
- Fehler, wenn Bedingung nicht erfüllt.

Beispiel‑Fehlertext:
- Kurz: "Pferd ist für diesen Bewerb zu jung."
- Lang: "Das Mindestalter für diesen Bewerb ist {X} Jahre (Stichtag 1. Jänner). Dieses Pferd gilt im aktuellen Jahr als {Y} Jahre alt."

### 4.4 Quellen/Verweise
- FEI General Regulations, Art. 136 (Age of Horses)
- ÖTO (disziplinspezifische Mindestalter nationaler Bewerbe)

### 4.5 Mindestalter‑Tabellen (DRAFT; Paragraphen‑Verweise finalisieren)
- Springen (national, ÖTO; Bezug § 231, Pferdealter allgemeine Bestimmungen):

| Höhe (cm) | Mindestalter Pferd (Jahre, Stichtag 1.1.) |
|---|---|
| ≤ 100 | 4 |
| 105–120 | 5 |
| ≥ 125 | 6 |

- Dressur (national, ÖTO; Bezug § 103, Pferdealter):

| Prüfungsniveau | Mindestalter Pferd |
|---|---|
| Einsteiger/Dressurreiter (niedrig) | 4 |
| A/L | 4 |
| LM/M | 5 |
| S | 6 |

- International (FEI, GR Art. 136 + Disziplinspezifische Regeln, exemplarisch):

| Disziplin | Prüfungs-/Star‑Level | Mindestalter |
|---|---|---|
| Jumping | 1*–2* | 6 |
| Jumping | 3*–5* | 7 |
| Dressage | CDI‑YH (Young Horses) | gem. FEI YH‑Regeln |
| Dressage | CDI (Senior) | 7 |

Hinweis: Exakte FEI‑Tabellen sind pro Disziplinregelwerk verbindlich zu übernehmen; hier nur Platzhalter bis Paragraphen‑Finalisierung.

---

## 5. Offene Punkte & Nächste Schritte
- Lizenz‑Zuordnungstabelle (Springen/Dressur) mit Paragraphen‑Verweisen finalisieren und hier einpflegen. (Status: DRAFT Tabellen vorhanden)  
- Mindestalter‑Tabellen je Disziplin und Klasse/Höhe aus ÖTO & FEI präzise ergänzen. (Status: DRAFT Tabellen vorhanden)  
- FEI‑Legacy‑Code → numerische ID Mappings in Masterdata‑SCS verankern; Backend‑Lookup implementieren. (Status: erste Version implementiert, JSON‑Mapping, REST‑Endpoint)

Meta:
- status: DRAFT (wird auf STABLE angehoben nach Fachfreigabe)
- version: 0.4 (2026‑04‑03)
