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

15 KiB
Raw Blame History

type status owner last_update
RULE_SPEC DRAFT Rulebook Expert 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-123456701234567 (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

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

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):

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:

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)