- Introduced `license_height_matrix` for mapping minimum license requirements to jump heights (ÖTO § 231). - Added `horse_min_age_matrix` for defining minimum horse ages by discipline, height, or level (ÖTO § 103, FEI GR Art. 136). - Populated both tables with ÖTO 2026 and FEI-compliant seed data. - Updated Flyway V008 to remove incorrect `RD4` entry and annotated corrected enum references. - Created Flyway V009 for introducing the new tables and their seeds. - Aligned documentation, validation rules, and roadmaps for backend implementation handover. Signed-off-by: Stefan Mogeritsch <stefan.mo.co@gmail.com>
301 lines
15 KiB
Markdown
301 lines
15 KiB
Markdown
---
|
||
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)
|