meldestelle/docs/03_Domain/02_Reference/Validierungsregeln.md
Stefan Mogeritsch c696b8c50e feat(db): add regulation-as-data tables for license height and horse age matrices
- 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>
2026-04-03 11:10:45 +02:00

301 lines
15 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
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 (LiveValidation), Backend (serverseitige Validation) und QA (Testfälle). Änderungen erfolgen versioniert und mit Beispielen.
Quellen/Verweise:
- Roadmap: `docs/04_Agents/Roadmaps/Rulebook_Roadmap.md`
- DomänenModell: `docs/03_Domain/01_Core_Model/Domain_Model_Veranstaltung_Turnier_Bewerb_Abteilung.md`
---
## 1. OEPSMitgliedsnummer (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 SyncSchnittstellen.
### 1.2 Kanonisches Format (Normalform)
- Zeichenraum: Großbuchstaben AZ, Ziffern 09, Bindestrich `-`.
- Erlaubte Schreibweisen (Regex in PCRENotation):
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/ValidationVersion.
### 1.6 PseudocodeValidierung
```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 (UXTexte)
- Kurz: "Ungültige OEPSMitgliedsnummer. Erlaubt sind 68 Ziffern, optional mit Präfix 'OEPS-'."
- Lang: "Bitte eine gültige OEPSMitgliedsnummer eingeben: 68 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 KartenformatVarianten? Falls ja, eigene LegacyRegex aufnehmen.
- QA: Testfälle für Grenzwerte (6/8 Ziffern), PräfixVarianten, WhitespaceTrimmung.
---
## 2. FEIID
Status: Draft auf Basis FEI General Regulations (Art. 113114) 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 FEIrelevanten 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: 78stellige numerische FEIIDs (z. B. `10011469`).
- Legacy/Referenzcode (in LegacyDaten 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 FEIID aufgelöst (siehe 2.5).
Nicht erlaubt:
- Leerzeichen, Trennzeichen, gemischte Schreibweisen mit Präfixen (z. B. `FEI-10011469`), alphanumerische Mischformen außerhalb des obigen LegacyMusters.
### 2.3 PflichtfelderRegel (Wann ist FEIID erforderlich?)
- International (FEIEvents: CI/CSI/CDI/CCI/CIO/CH/…):
- Athlet: FEIID Pflicht.
- Pferd: FEIID Pflicht (inkl. FEIPass/Microchip gem. FEIRegeln, vgl. Art. 114, 137 FEI GR).
- National (ÖTOEvents: CN/CSN/CDN/CCN):
- Athlet: FEIID optional (nur wenn FEIregistriert).
- Pferd: FEIID optional (nur wenn FEIregistriert).
- Ausnahme: Wenn eine nationale Prüfung als FEIqualifikationsrelevant ausgewiesen ist, kann FEIID für Datenexporte empfohlen/erforderlich sein (Veranstalterhinweis).
Hinweis: Die konkrete Pflicht koppeln wir im System an das Feld „Turnierkategorie“ und Disziplin, konfigurierbar per RegelSet.
### 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 FEIID (`[0-9]{7,8}`) als String ohne Trennzeichen.
- LegacyReferenzcode wird sofern möglich vor Speicherung via Mapping/Lookup in numerische FEIID überführt. Falls kein Mapping möglich, speichern als eingegeben plus `source_format = LEGACY_CODE`.
### 2.6 PseudocodeValidierung
```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 (UXTexte)
- Kurz: "Ungültige FEIID. Erlaubt sind 78 Ziffern (z. B. 10011469)."
- Lang: "Bitte eine gültige FEIID eingeben: 78 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 FrontendStores (FEIIDs): `frontend/shells/meldestelle-desktop/.../Stores.kt`
### 2.9 BackendLookup (MasterdataSCS)
- Endpoint: `GET /api/fei/resolve/{id}`
- Eingabe: `{id}` numerisch (`^[0-9]{7,8}$`) oder LegacyCode (`^[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 }`
- MappingQuelle: `backend/services/masterdata/masterdata-service/src/main/resources/data/fei-id-mapping.json` (kann später aus DB gespeist werden).
---
## 3. Lizenzklassen (R1R4, RD1RD3, LIZENZFREI)
Status: Draft basierend auf ÖTOPraxis und ZNSLizenzdaten. Detaillierte ParagraphenZitate werden nachgereicht (A2/A3 Arbeiten verknüpft).
### 3.1 Katalog gültiger Lizenzklassen
- Reiten Springen (RKlassen): `R1`, `R2`, `R3`, `R4`
- Dressur Reiten (RDKlassen): `RD1`, `RD2`, `RD3`
- Lizenzfrei/ohne Lizenz Kennzeichnung: `LIZENZFREI` (Enum-Key in `LizenzKlasseE`; Label: „ohne Lizenz“)
Erweiterbarkeit: Weitere Spezial/Jugend oder FahrerLizenzen 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 A2 (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 (UXTexte)
- 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)
- ZNSLizenzdaten: `docs/OePS/ZNS/LIZENZ01.dat` (Datenquelle, strukturierter Export) Parsing/Anlage in MasterdataSCS.
- Teilungs-/Warnlogik: `docs/03_Domain/02_Reference/OETO_Regelwerk/Warn-Logik-Spezifikation-competition-context.md`
### 3.6 LizenzZuordnungstabelle (DRAFT, final mit ParagraphenVerweisen)
- 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 |
| 105110 | R1, R2+ (Empf. R2) | § 231 |
| 115120 | R2+ | § 231 |
| 125135 | 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 CSNCNEU sind in A2 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. 56 Jahre (konkret per Tabelle nachzureichen)
- International (FEI, vgl. Art. 136 GR):
- Disziplinspezifische Mindestalter (werden tabellarisch hinterlegt; Abhängig von Disziplin/Testlevel/StarRating).
Hinweis: Konkrete, rechtssichere Tabellen (Disziplin × Klasse/Höhe × Mindestalter) werden nach ParagraphenSichtung ergänzt und in MasterdataSCS versioniert.
### 4.3 Validierungslogik
- Errechne `age = horseAgeOnJan1(geburtsjahr, veranstaltungsjahr)`.
- Prüfe `age >= minAgeFor(discipline, heightCm?, testLevel?)` laut Matrix.
- Fehler, wenn Bedingung nicht erfüllt.
BeispielFehlertext:
- 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 MindestalterTabellen (DRAFT; ParagraphenVerweise finalisieren)
- Springen (national, ÖTO; Bezug § 231, Pferdealter allgemeine Bestimmungen):
| Höhe (cm) | Mindestalter Pferd (Jahre, Stichtag 1.1.) |
|---|---|
| ≤ 100 | 4 |
| 105120 | 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-/StarLevel | Mindestalter |
|---|---|---|
| Jumping | 1*2* | 6 |
| Jumping | 3*5* | 7 |
| Dressage | CDIYH (Young Horses) | gem. FEI YHRegeln |
| Dressage | CDI (Senior) | 7 |
Hinweis: Exakte FEITabellen sind pro Disziplinregelwerk verbindlich zu übernehmen; hier nur Platzhalter bis ParagraphenFinalisierung.
---
## 5. Offene Punkte & Nächste Schritte
- LizenzZuordnungstabelle (Springen/Dressur) mit ParagraphenVerweisen finalisieren und hier einpflegen. (Status: DRAFT Tabellen vorhanden)
- MindestalterTabellen je Disziplin und Klasse/Höhe aus ÖTO & FEI präzise ergänzen. (Status: DRAFT Tabellen vorhanden)
- FEILegacyCode → numerische ID Mappings in MasterdataSCS verankern; BackendLookup implementieren. (Status: erste Version implementiert, JSONMapping, RESTEndpoint)
Meta:
- status: DRAFT (wird auf STABLE angehoben nach Fachfreigabe)
- version: 0.4 (20260403)