docs: massive restructuring of documentation, development guides and agent playbooks

This commit is contained in:
2026-06-15 12:54:38 +02:00
parent e4988b4397
commit ce63303b2c
686 changed files with 45423 additions and 319 deletions
@@ -0,0 +1,179 @@
---
type: DOMAIN_SPEC
status: ACTIVE
owner: Lead Architect
last_update: 2026-04-02
---
# DomänenModell: Veranstaltung → Turnier → Bewerb → Abteilung
Ziel: Dieses Dokument fixiert das offizielle KernModell für die EventStruktur sowie Kassa/Konten. Es ist die Single Source of Truth für BackendSchema, FrontendViewModels und Schnittstellen.
Quellen/Verweise:
- Ubiquitous Language: `docs/03_Domain/01_Glossary/Ubiquitous_Language.md`
- ÖTO/FEI Referenz: `docs/03_Domain/02_Reference/` (insb. AbteilungsSchwellenwerte)
- ADR0021 TenantResolution (EventIsolation): `docs/01_Architecture/adr/0021-tenant-resolution-strategy-de.md`
## 1. Struktur und Kardinalitäten
Hierarchie und Identifikatoren (kanonisch):
```
Veranstaltung (event_id)
├─ Turnier (tournament_id) [1:N pro Veranstaltung]
│ └─ Bewerb (class_id) [1:N pro Turnier]
│ └─ Abteilung (division_id) [1:N pro Bewerb]
└─ TeilnehmerKonto (account_id) [1:N pro Veranstaltung, referenziert Teilnehmer]
└─ VeranstaltungsKassa (event_cashbox_id = event_id) [1:1]
```
Leitlinien:
- Jede Veranstaltung ist ein eigener Tenant (SchemaperTenant gemäß ADR0021).
- IDs sind innerhalb des Tenants eindeutig; globale Adressen entstehen durch `{event_id}/{local_id}`.
## 2. Entitäten und Aggregate
### 2.1 Veranstaltung
- Schlüssel: `event_id` (Slug, z. B. `2026-moc-open`)
- AggregateGrenze: umfasst Metadaten der Veranstaltung, Kassa, TeilnehmerKontoKatalog.
- Invarianten:
- `status ∈ {draft, active, archived}`
- Archivierte Veranstaltungen sind readonly.
### 2.2 Turnier
- Schlüssel: `tournament_id` (innerhalb Veranstaltung eindeutig)
- Attribute (Auszug): Titel, Datum(e), Ort, Status.
- Invarianten:
- Ein Turnier gehört genau zu einer Veranstaltung.
- Löschen nur erlaubt, wenn keine Nennungen/Ergebnisse bestätigt sind.
### 2.3 Bewerb
- Schlüssel: `class_id`
- Attribute: Disziplin, Klasse, Lizenzanforderungen, max Starter, Wertungsmodus.
- Invarianten:
- Ein Bewerb gehört genau zu einem Turnier.
- Abteilungsbildung erfolgt gemäß Regelwerk/Schwellenwerten.
### 2.4 Abteilung
- Schlüssel: `division_id`
- Attribute: Lauf/Startzeit, Parcours/Bahn, Typ (siehe unten), Ergebnisstatus.
- Invarianten:
- Eine Abteilung gehört genau zu einem Bewerb.
- Typen steuern UI, Zeitplan und Preisgeld-/Siegerehrungslogik.
### 2.5 TeilnehmerKonto (auf Veranstaltungsebene)
- Zweck: Vereinheitlichte finanzielle Sicht je Teilnehmer über mehrere Turniere derselben Veranstaltung (MultiTurnier).
- Schlüssel: `account_id`
- Beziehungen:
- `Teilnehmer` (z. B. Reiter, Verein, Team) 1:1 ↔ TeilnehmerKonto (pro Veranstaltung)
- Buchungen entstehen aus Nennungen, Startgeldern, Gebühren, Gutschriften, Rückzahlungen turnierübergreifend.
- Invarianten:
- Ein Teilnehmer hat höchstens ein Konto pro Veranstaltung.
- Saldo ist Summe aller bestätigten Buchungen innerhalb des Tenants.
### 2.6 VeranstaltungsKassa (Turnier‑übergreifender Saldo)
- Zweck: Aggregierte Kasse der gesamten Veranstaltung; spiegelt Einzahlungen/Auszahlungen und Summen über alle Turniere.
- Schlüssel: `event_cashbox_id` = `event_id`
- Komponenten:
- Journal (Belege): Ein/Auszahlungen, Umbuchungen, Korrekturen.
- Summen: aktueller Bestand, Reserven, offene Posten (aggregiert aus TeilnehmerKonten).
- Invarianten:
- Jede Buchung betrifft genau ein Gegenkonto (TeilnehmerKonto oder internes Konto).
- Journal ist unveränderlich; Korrekturen erfolgen als Gegenbuchung.
## 3. AbteilungsTypen
Definiert als `enum DivisionType`:
- `STANDARD`: Normale Abteilung mit regulärer Siegerehrung innerhalb des Bewerbs.
- `SEPARATE_SIEGEREHRUNG`: Abteilung, deren Siegerehrung separat organisiert wird (z. B. zusammengelegt/zeitlich entkoppelt) — STATUS: vorläufig, Detailregeln folgen durch 📜 Rulebook Expert.
- `ORGANISATORISCH`: Rein organisatorische Abteilung (z. B. Aufteilung aus Zeit/PlatzGründen), ohne eigenständige sportliche Wertung/Preisgeldlogik.
Hinweis: Die genaue Ausgestaltung von `SEPARATE_SIEGEREHRUNG` (PreisgeldAggregation, RankingAnzeige, Protokoll) wird im RulebookDokument ergänzt und kann weitere Felder/Beziehungen erfordern (z. B. Verweis auf „gemeinsame Siegerehrung für Bewerbe X/Y“).
## 4. DatenmodellSkizze (relationale Sicht je Tenant)
```sql
-- Veranstaltung (im TenantSchema)
CREATE TABLE event (
event_id TEXT PRIMARY KEY,
title TEXT NOT NULL,
status TEXT NOT NULL CHECK (status IN ('draft','active','archived'))
);
CREATE TABLE tournament (
tournament_id TEXT PRIMARY KEY,
event_id TEXT NOT NULL REFERENCES event(event_id),
title TEXT NOT NULL,
start_date DATE,
end_date DATE,
status TEXT NOT NULL
);
CREATE TABLE class (
class_id TEXT PRIMARY KEY,
tournament_id TEXT NOT NULL REFERENCES tournament(tournament_id),
discipline TEXT NOT NULL,
level TEXT NOT NULL,
max_starters INT,
scoring_mode TEXT NOT NULL
);
CREATE TABLE division (
division_id TEXT PRIMARY KEY,
class_id TEXT NOT NULL REFERENCES class(class_id),
type TEXT NOT NULL CHECK (type IN ('STANDARD','SEPARATE_SIEGEREHRUNG','ORGANISATORISCH')),
scheduled_at TIMESTAMP,
status TEXT NOT NULL
);
-- TeilnehmerKonto (veranstaltungsweit)
CREATE TABLE participant_account (
account_id TEXT PRIMARY KEY,
event_id TEXT NOT NULL REFERENCES event(event_id),
participant_ref TEXT NOT NULL, -- verweist auf TeilnehmerStammdatensatz im Tenant
UNIQUE(event_id, participant_ref)
);
CREATE TABLE participant_ledger_entry (
entry_id TEXT PRIMARY KEY,
account_id TEXT NOT NULL REFERENCES participant_account(account_id),
booking_ts TIMESTAMP NOT NULL,
amount_cents BIGINT NOT NULL,
currency TEXT NOT NULL DEFAULT 'EUR',
source TEXT NOT NULL, -- z. B. Nennung, Startgeld, Rückzahlung
tournament_id TEXT NULL REFERENCES tournament(tournament_id)
);
-- VeranstaltungsKassa
CREATE TABLE event_cashbox (
event_cashbox_id TEXT PRIMARY KEY REFERENCES event(event_id),
created_at TIMESTAMP NOT NULL
);
CREATE TABLE cashbox_journal (
journal_id TEXT PRIMARY KEY,
event_cashbox_id TEXT NOT NULL REFERENCES event_cashbox(event_cashbox_id),
booking_ts TIMESTAMP NOT NULL,
amount_cents BIGINT NOT NULL,
direction TEXT NOT NULL CHECK (direction IN ('IN','OUT')),
counterparty TEXT NOT NULL, -- account_id oder internes Konto
memo TEXT
);
```
## 5. Invarianten und Geschäftsregeln (Auszug)
- AbteilungsTyp `ORGANISATORISCH` darf keine eigenständige Preisgeldlogik auslösen.
- `SEPARATE_SIEGEREHRUNG` kann Ergebnisse bündeln/verschieben; Detailregeln werden im Rulebook spezifiziert. Bis dahin bleiben APIFelder stabil, Verhalten konservativ (keine automatische Zusammenlegung ohne explizite Verknüpfung).
- TeilnehmerKontoSaldo = Summe aller bestätigten `participant_ledger_entry.amount_cents`.
- EventKassaBestand = Summe `IN` Summe `OUT`; regelmäßige Abstimmung mit Summe aller TeilnehmerOffenen Posten.
## 6. API/DTO Richtlinien (HighLevel)
- Alle APIRessourcen werden unterhalb des Tenants adressiert (Header `X-Event-Id`).
- DTOs tragen stabile `*_id` Felder entsprechend diesem Modell; Referenzen sind per ID, keine eingebetteten Aggregate außer ReadViews.
- Enum `DivisionType` wird exakt wie oben benannt; neue Typen erfordern Versionserhöhung des Schemas.
## 7. ToDos und Folgearbeiten
- 📜 Rulebook Expert: DetailSpezifikation `SEPARATE_SIEGEREHRUNG` (Preisgeld, Ranking, UIHinweise) ergänzen.
- 🧹 Curator: `Ubiquitous_Language.md` um obige Begriffe/Definitionen erweitern.
- 👷 Backend: SchemaMigrationen pro Tenant gemäß obiger Tabellen; Repositories/Services entsprechend zuschneiden.
- 🎨 Frontend: ViewModels/Stores entlang dieser Struktur aktualisieren (Navigation: Veranstaltung → Turnier → Bewerb → Abteilung).
@@ -0,0 +1,241 @@
-- Database Schema Draft for Meldestelle (Offline-First)
-- Dialect: SQLite (compatible with SQLDelight)
-- Status: Draft / Proposal
-- Based on: OEPS Legacy Spec V2.4 & Domain Analysis
-- ==================================================================
-- 1. CORE INFRASTRUCTURE (Sync & Audit)
-- ==================================================================
-- Every table should ideally have these fields, but for brevity
-- they are implied or added where critical.
-- id: TEXT NOT NULL PRIMARY KEY (UUID)
-- created_at: INTEGER NOT NULL (Epoch Millis)
-- updated_at: INTEGER NOT NULL (Epoch Millis)
-- version: INTEGER NOT NULL (Optimistic Locking / Sync Counter)
-- is_deleted: INTEGER NOT NULL DEFAULT 0 (Soft Delete)
-- ==================================================================
-- 2. MASTER DATA (Stammdaten)
-- ==================================================================
-- Akteure: Personen und Organisationen
-- Covers: Reiter, Richter, Besitzer, Vereine
CREATE TABLE actor (
id TEXT NOT NULL PRIMARY KEY,
type TEXT NOT NULL, -- 'PERSON', 'ORGANIZATION'
-- Display Data
first_name TEXT, -- NULL for Organizations
last_name TEXT NOT NULL, -- Name or Org-Name
-- OEPS Specifics (Legacy Spec)
oeps_id TEXT, -- 'Satznummer' (6 digits for Person, 4 for Club)
oeps_category TEXT, -- 'Verein', 'Reiter', 'Richter'
-- Licenses & Status
license_code TEXT, -- e.g. 'R1', 'RD3'
has_start_card INTEGER NOT NULL DEFAULT 0, -- Boolean: Paid annual fee?
is_locked INTEGER NOT NULL DEFAULT 0, -- Boolean: Sperrliste?
-- Contact & Meta
nationality TEXT NOT NULL DEFAULT 'AUT', -- ISO 3-Letter
contact_json TEXT, -- Address, Phone, Email
-- Sync Meta
created_at INTEGER NOT NULL,
updated_at INTEGER NOT NULL,
version INTEGER NOT NULL DEFAULT 1
);
CREATE INDEX idx_actor_oeps_id ON actor(oeps_id);
CREATE INDEX idx_actor_name ON actor(last_name, first_name);
-- Pferde
CREATE TABLE horse (
id TEXT NOT NULL PRIMARY KEY,
name TEXT NOT NULL,
-- Identification
oeps_id TEXT, -- 'Satznummer' (10 digits) - CRITICAL for Export
head_number_permanent TEXT, -- 'Kopfnummer' (e.g. A123)
life_number TEXT, -- 'Lebensnummer' (Zucht)
fei_id TEXT,
-- Details
birth_year INTEGER,
gender TEXT, -- 'M', 'W', 'G' (Gelding/Wallach)
color TEXT,
sire_name TEXT, -- Vater (Denormalized for search)
dam_name TEXT, -- Mutter
-- Owner Link
owner_id TEXT, -- FK to actor.id
-- Status
is_locked INTEGER NOT NULL DEFAULT 0, -- Sperrliste
-- Sync Meta
created_at INTEGER NOT NULL,
updated_at INTEGER NOT NULL,
version INTEGER NOT NULL DEFAULT 1
);
CREATE INDEX idx_horse_oeps_id ON horse(oeps_id);
CREATE INDEX idx_horse_head_num ON horse(head_number_permanent);
CREATE INDEX idx_horse_name ON horse(name);
-- ==================================================================
-- 3. EVENT STRUCTURE
-- ==================================================================
CREATE TABLE event (
id TEXT NOT NULL PRIMARY KEY,
name TEXT NOT NULL,
start_date INTEGER NOT NULL, -- Epoch Day
end_date INTEGER NOT NULL,
location TEXT,
organizer_id TEXT NOT NULL, -- FK to actor.id
status TEXT NOT NULL DEFAULT 'PLANNING' -- PLANNING, ACTIVE, ARCHIVED
);
CREATE TABLE tournament (
id TEXT NOT NULL PRIMARY KEY,
event_id TEXT NOT NULL REFERENCES event(id),
-- OEPS Spec
oeps_number TEXT NOT NULL, -- 5 digits (e.g. 21001)
category TEXT, -- e.g. 'CSN-A'
ruleset TEXT NOT NULL DEFAULT 'OETO', -- 'OETO', 'FEI'
-- Sync Meta
updated_at INTEGER NOT NULL
);
-- Bewerbe (Competitions)
-- Note: If a competition is split into 2 departments (Abteilungen),
-- we create 2 rows here to match the OEPS 'B-Satz' logic.
CREATE TABLE competition (
id TEXT NOT NULL PRIMARY KEY,
tournament_id TEXT NOT NULL REFERENCES tournament(id),
-- Identification
code_internal TEXT NOT NULL, -- '01', '02' (2 digits)
code_official TEXT, -- '001' (3 digits, optional)
division_id INTEGER NOT NULL DEFAULT 0, -- 'Abteilung' (0=None, 1=1st, 2=2nd)
-- Description
title TEXT NOT NULL,
category TEXT, -- e.g. 'LM', 'S*'
discipline TEXT NOT NULL, -- 'D', 'S', 'C' (Dressage, Jumping, Combined)
-- Rules & Scoring
scoring_method TEXT NOT NULL, -- 'A0', 'C', 'DRESSAGE_PERCENT'
start_fee INTEGER NOT NULL DEFAULT 0, -- In Cents
-- State
status TEXT NOT NULL DEFAULT 'OPEN', -- OPEN, CLOSED_FOR_ENTRIES, RUNNING, FINISHED, SIGNED_OFF
-- Sync Meta
updated_at INTEGER NOT NULL
);
CREATE INDEX idx_comp_tournament ON competition(tournament_id);
-- ==================================================================
-- 4. SPORT & PROCESS
-- ==================================================================
-- Nennungen (Entries)
-- Represents the intent to start.
CREATE TABLE entry (
id TEXT NOT NULL PRIMARY KEY,
competition_id TEXT NOT NULL REFERENCES competition(id),
-- The Pair
horse_id TEXT NOT NULL REFERENCES horse(id),
rider_id TEXT NOT NULL REFERENCES actor(id),
-- Financials
responsible_person_id TEXT REFERENCES actor(id), -- Who pays?
fee_agreed INTEGER NOT NULL, -- In Cents (Snapshot of price at entry time)
payment_status TEXT NOT NULL DEFAULT 'PENDING', -- PENDING, PAID, WAIVED
-- Validation Override (The "Human Factor")
validation_status TEXT NOT NULL DEFAULT 'OK', -- OK, WARNING, BLOCKED
override_comment TEXT, -- Why was this allowed despite warning?
-- Sync Meta
created_at INTEGER NOT NULL,
updated_at INTEGER NOT NULL
);
CREATE INDEX idx_entry_comp ON entry(competition_id);
CREATE INDEX idx_entry_rider ON entry(rider_id);
-- Startliste (Start Order)
-- Subset of entries that actually start.
CREATE TABLE start_list_entry (
id TEXT NOT NULL PRIMARY KEY,
entry_id TEXT NOT NULL REFERENCES entry(id),
-- Ordering
start_order INTEGER, -- 1, 2, 3...
start_time_planned INTEGER, -- Epoch Millis (optional)
-- Tournament Specifics
head_number_event TEXT, -- Startnummer am Turnier (kann von A123 abweichen)
-- Status
status TEXT NOT NULL DEFAULT 'READY', -- READY, STARTED, DNS (Did Not Start)
UNIQUE(entry_id)
);
-- Ergebnisse (Results)
CREATE TABLE result (
id TEXT NOT NULL PRIMARY KEY,
start_list_entry_id TEXT NOT NULL REFERENCES start_list_entry(id),
-- The Outcome
rank INTEGER, -- 1, 2, 3... (NULL if eliminated)
-- Scoring Details (Polymorphic based on Competition Type)
points_jump_faults DECIMAL(5,2), -- Springfehler
time_taken_ms INTEGER, -- Zeit in Millisekunden
score_dressage_percent DECIMAL(5,3), -- 72.500
score_dressage_total DECIMAL(6,2), -- Summe Punkte
-- Status Flags
classification TEXT NOT NULL DEFAULT 'OK', -- OK, EL (Elim), RET (Retired), DIS (Disq)
-- Detailed Marks (JSON)
-- e.g. { "judge_C": 7.5, "judge_H": 7.2, "obstacles": [...] }
details_json TEXT,
-- Money
prize_money INTEGER DEFAULT 0, -- In Cents
-- Audit
updated_by_user_id TEXT,
updated_at INTEGER NOT NULL
);
CREATE INDEX idx_result_starter ON result(start_list_entry_id);
-- ==================================================================
-- 5. AUDIT LOG (NFR-07)
-- ==================================================================
CREATE TABLE audit_log (
id TEXT NOT NULL PRIMARY KEY,
entity_type TEXT NOT NULL, -- 'RESULT', 'ENTRY'
entity_id TEXT NOT NULL,
action TEXT NOT NULL, -- 'CREATE', 'UPDATE', 'DELETE'
user_id TEXT,
timestamp INTEGER NOT NULL,
changes_json TEXT -- { "score_old": 7.0, "score_new": 7.5 }
);
@@ -0,0 +1,177 @@
# Turnier- & Bewerbsstruktur Diagramm
Dieses Diagramm zeigt die strukturelle Hierarchie und die Beziehungen zwischen Event, Turnier und Bewerb basierend auf
dem ÖTO-Regelwerk und den Anforderungen der OEPS-Schnittstelle.
```mermaid
erDiagram
%% Entities
EVENT {
string id PK "UUID"
string name "z.B. Apropos Pferd 2026"
date start_date
date end_date
string location
string organizer_id FK "Veranstalter (Akteur)"
string status "PLANNING, ACTIVE, ARCHIVED"
}
TOURNAMENT {
string id PK "UUID"
string event_id FK
string oeps_number "z.B. 21001 (A-Satz)"
string category "z.B. CSN-A, CDN-B, CDN-C-NEU"
string ruleset "OETO oder FEI"
}
COMPETITION {
string id PK "UUID"
string tournament_id FK
string code_internal "2-stellig (B-Satz Pos. 2)"
string code_official "3-stellig (B-Satz Pos. 61)"
int division_id "Abteilung (0=keine, 1=Abt. 1...)"
string title "z.B. Standardspringprüfung"
string discipline "S (Springen), D (Dressur), C"
string category "Klasse z.B. A, L, M, S*, lizenzfrei"
string scoring_method "Richtverfahren (z.B. A0, A2, AM5, Stil)"
int start_fee "in Cent"
string status "OPEN, RUNNING, FINISHED"
int planned_duration_per_starter_seconds "Kalkulierte Reitzeit pro Starter"
}
OFFICIALS {
string id PK "UUID"
string tournament_id FK "oder competition_id"
string actor_id FK "Richter / Parcoursbauer"
string role "Hauptrichter, Richter, TD, Parcoursbauer"
}
ACTOR {
string id PK "UUID"
string oeps_id "z.B. 123456"
string type "PERSON, ORGANIZATION"
string name "Vollständiger Name"
string license_code "z.B. R1"
}
QUALIFICATION {
string id PK "UUID"
string actor_id FK
string discipline "S, D, V"
string level "z.B. L, M, S"
string role "RICHTER, PARCOURSBAUER"
}
ENTRY {
string id PK "UUID"
string competition_id FK
string horse_id FK
string rider_id FK
string status "ACTIVE, WITHDRAWN"
string preferred_start_position "Optional: vorne, hinten, zu"
}
START_LIST_ENTRY {
string id PK "UUID"
string entry_id FK
int start_order "Nummer in der Startreihenfolge"
string status "READY, IN_PROGRESS, FINISHED, DNS"
}
RESULT {
string id PK "UUID"
string start_list_entry_id FK
int rank "Platzierung (kann manuell überschrieben werden)"
string status "OK, EL, RET, DNS"
float points "Fehlerpunkte / Wertnote"
float time_seconds "Benötigte Zeit"
int prize_money "Ausbezahltes Geld in Cent"
}
BILLING_ACCOUNT {
string id PK "UUID"
string event_id FK
string payer_id FK "Akteur (Zahler)"
int current_balance "in Cent"
}
TRANSACTION {
string id PK "UUID"
string account_id FK
string entry_id FK "Optionaler Bezug zur Nennung"
string type "NENNGELD, STARTGELD, TAUSCHGEBÜHR, NACHNENNUNG"
int amount "in Cent"
}
%% Relationships
EVENT ||--o{ TOURNAMENT: "beinhaltet"
TOURNAMENT ||--o{ COMPETITION: "besteht aus (B-Satz)"
TOURNAMENT ||--o{ OFFICIALS: "hat Funktionäre (C-Satz)"
COMPETITION ||--o{ OFFICIALS: "wird gerichtet von"
OFFICIALS }o--|| ACTOR: "ist ein"
ACTOR ||--o{ QUALIFICATION: "hat Berechtigungen"
COMPETITION ||--o{ ENTRY: "hat Nennungen"
ENTRY ||--o| START_LIST_ENTRY: "wird zu Starter"
START_LIST_ENTRY ||--o| RESULT: "hat Ergebnis"
EVENT ||--o{ BILLING_ACCOUNT: "verwaltet Kassa für"
BILLING_ACCOUNT ||--o{ TRANSACTION: "verbucht"
```
## Erläuterung zum Modell
### 1. `EVENT` (Veranstaltung)
Das **Event** ist der übergeordnete organisatorische Rahmen (z.B. "Pferdefestival 2026"). Es hat ein Datum, einen Ort
und einen Veranstalter. Es existiert unabhängig von den strikten OEPS-Regularien und ist primär für die administrative
Verwaltung (Rechnungsstellung, Anlagenplanung) wichtig.
### 2. `TOURNAMENT` (Turnier)
Ein Event kann mehrere **Turniere** beinhalten (z.B. ein nationales CSN-B und gleichzeitig ein internationales CSI2*).
Das Turnier ist die Instanz, die strikt an das Regelwerk gebunden ist:
* Es korrespondiert 1:1 mit dem **A-Satz** des OEPS-Pflichtenhefts.
* Es hat eine eindeutige 5-stellige `oeps_number`.
* Es legt fest, nach welchem Regelwerk (ÖTO vs. FEI) geritten und ausgewertet wird. In der ersten Phase konzentrieren
wir uns auf **C-NEU** und **C**.
### 3. `COMPETITION` (Bewerb / Prüfung)
Die kleinste sportliche Einheit und das Herzstück der Ausschreibung. Wir fokussieren uns initial auf **Dressur (D)** und
**Springen (S)**.
Hier finden wir die direkte Verbindung zum **B-Satz** der Legacy-Spezifikation:
* **Abteilungen (`division_id`):** Laut OEPS-Schnittstelle wird jede Abteilung (z.B. R1-Reiter getrennt von R2-Reitern)
datentechnisch fast wie ein eigener Bewerb behandelt. In unserer Datenbank repräsentieren wir jede Abteilung als
eigene Zeile in der Tabelle `competition` (oder verknüpfen sie intelligent), da jede Abteilung ihre eigene
Ergebnisliste und ihr eigenes Preisgeld hat.
* **Nummerierung:** Intern 2-stellig (`code_internal`), für Turniere ab 100 Bewerben offiziell 3-stellig (
`code_official` an Stelle 61).
* **Richtverfahren:** Entscheidet darüber, wie Fehler und Zeit (Springen) oder Wertnoten (Dressur) in ein Ranking
übersetzt werden.
* **Zeitplanung:** `planned_duration_per_starter_seconds` ist für die Kalkulation der Startzeiten elementar.
### 4. `OFFICIALS` & `QUALIFICATION` (Der C-Satz & ZNS-Import)
Dies entspricht dem **C-Satz**. Richter und Parcoursbauer müssen einem Turnier oder spezifisch einem Bewerb zugewiesen
werden.
* Neu: Die Entität `QUALIFICATION` bildet die importierten Lizenzstufen aus der ZNS-Datei `RICHT01.DAT` ab. Das Backend
gleicht diese Berechtigungen bei der Zuweisung gegen die Kategorie der `COMPETITION` ab.
### 5. `ENTRY`, `START_LIST_ENTRY` & `RESULT` (Nennung bis Ergebnis)
* **`ENTRY` (Nennung):** Die Nennung verknüpft ein Pferd und einen Reiter mit einem bestimmten Bewerb. Hier werden auch
**Startwünsche** (vorne/hinten) erfasst, die für den Telefon-Workflow kritisch sind.
* **`START_LIST_ENTRY` (Startliste):** Generiert aus den aktiven Nennungen. Definiert die exakte Startreihenfolge.
* **`RESULT` (Ergebnis):** Hält die im "Richterturm-Workflow" erfassten Rohdaten (Punkte/Zeit) und die errechnete
Platzierung. Die Platzierung kann bei Bedarf manuell vom Veranstalter überschrieben werden (z.B. wenn mehr Platziert
werden sollen, als die ÖTO vorschlägt).
### 6. Billing Context (`BILLING_ACCOUNT` & `TRANSACTION`)
Die Abrechnung (Kassa) wird als separater Bereich an das `EVENT` gehängt.
* Es wird ein `BILLING_ACCOUNT` pro "Zahler" (meist ein `ACTOR`) geführt.
* Jede Gebühr (Nenngeld, Startgeld, Nachnenngebühr) wird als atomare `TRANSACTION` verbucht.
* Wenn ein **Nennungstausch** stattfindet, bleibt das Nenngeld als positive Transaktion auf dem Account erhalten,
während für die neue Nennung lediglich eine neue Startgeld- oder Tauschgebühr-Transaktion erzeugt wird. Das ermöglicht
maximale Flexibilität.
@@ -0,0 +1,141 @@
---
type: Reference
status: ACTIVE
owner: Lead Architect
---
# 01 - Core Domain Entities
Dieses Dokument definiert die zentralen fachlichen Entitäten (Kern-Entitäten) des "Meldestelle"-Projekts. Diese Entitäten bilden das Fundament des Datenmodells und der gesamten Anwendungslogik.
> **Hinweis:** Dieses Modell wurde basierend auf der Analyse des OEPS-Pflichtenhefts 2021 V2.4 verfeinert.
## Die 6 Kern-Entitäten
1. **Event**: Der organisatorische Rahmen.
2. **Turnier**: Die administrative, regelbasierte Einheit.
3. **Bewerb**: Die einzelne sportliche Prüfung.
4. **Wertungsserie**: Der übergeordnete Cup oder die Meisterschaft.
5. **Akteur**: Personen und Organisationen.
6. **Pferd**: Die Pferde als eigenständige Entität.
---
### 1. Entität: `Veranstaltung` (Event)
**Zweck:** Der übergeordnete organisatorische Container für eine Veranstaltung an einem bestimmten Ort und zu einer bestimmten Zeit. Eine Veranstaltung kann ein oder mehrere Turniere umfassen.
**Beispiele:** "Apropos Pferd 2026", "Vereinsturnier Reitclub XY".
**Attribute:**
* `Veranstaltung-ID` (PK): Eindeutiger technischer Schlüssel (UUID).
* `Name`: Offizieller Name der Veranstaltung.
* `Veranstaltungsort`: Adresse und Name der Anlage.
* `Datum_Von`: Startdatum des Events.
* `Datum_Bis`: Enddatum des Events.
* `Veranstalter_ID` (FK): Verweis auf den `Akteur`, der die Veranstaltung ausrichtet.
* `Sparten`: Liste der angebotenen Sparten.
* `Austragungsplätze`: Liste der Austragungsplätze (`austragungsplaetze`).
* `Artikel-Preisliste`: Liste der Zusatzartikel inkl. Preise (`artikelPreisliste`).
* `Status`: Grober Zustand des Events (z.B. `In Planung`, `Laufend`, `Abgeschlossen`).
---
### 2. Entität: `Turnier`
**Zweck:** Definiert eine administrative Einheit innerhalb eines Events, die unter einem einheitlichen Regelwerk stattfindet. Hier werden Nennungen, Starter- und Ergebnislisten verwaltet.
**Beispiele:** "CSN-A im Rahmen der Apropos Pferd", "CSI2* im Rahmen der Apropos Pferd".
**Attribute:**
* `Turnier-ID` (PK): Eindeutiger technischer Schlüssel (UUID).
* `Veranstaltung_ID` (FK): Verweis auf die übergeordnete `Veranstaltung`.
* `Turniernummer_OEPS`: 5-stellige Nummer (z.B. `21001`) für den Datenaustausch.
* `Reglement`: Entscheidende Weiche für die Anwendungslogik (Enum: `OETO`, `FEI`).
* `Kategorie`: Offizielle Turnierkategorie (z.B. "CSN-A", "CSI2*", "CDI-W").
* `Sparte`: Sparte des Turniers (z.B. `Springen`, `Dressur`).
* `Turnierbeauftragter_ID` (FK): Referenz auf den Turnierbeauftragten (TB).
* `Ausschreibung_Text`: Der vollständige Text der Ausschreibung.
* `Nennschluss`: Datum und Uhrzeit.
* `NachnenngebuehrVerlangt`: Flag, ob Nachnenngebühr erhoben wird.
* `NenntauschboerseAktiv`: Flag, ob Nenntauschbörse aktiv ist.
* `Status`: Detaillierter Zustand des Turniers (z.B. `Genehmigt`, `Nennschluss`, `Ergebnisse final`).
---
### 3. Entität: `Bewerb`
**Zweck:** Die einzelne sportliche Prüfung innerhalb eines Turniers. Ein Bewerb ist die kleinste Einheit, für die eine Nennung möglich ist und eine Ergebnisliste erstellt wird.
**Beispiele:** "Standardspringprüfung Kl. L", "Dressurprüfung Kl. M - Aufgabe M5".
**Attribute:**
* `Bewerb-ID` (PK): Eindeutiger technischer Schlüssel (UUID).
* `Turnier_ID` (FK): Verweis auf das zugehörige `Turnier`.
* `Nummer_Intern`: 2-stellige Nummer (z.B. `05`).
* `Nummer_Offiziell`: 3-stellige Nummer (z.B. `005`) für Turniere > 99 Bewerbe.
* `Abteilung`: Kennzeichen für Unterteilungen (z.B. `1`, `2`). Default `0`.
* `Titel`: Der offizielle Titel des Bewerbs.
* `Startgeld`: Das für diesen Bewerb zu entrichtende Startgeld (in EUR).
* `Startberechtigung_Text`: Textuelle Beschreibung der Teilnahmevoraussetzungen.
* `Besondere_Bestimmungen`: Spezielle Regeln nur für diesen Bewerb.
---
### 4. Entität: `Wertungsserie`
**Zweck:** Definiert eine übergeordnete Wertung (Cup, Meisterschaft), die Ergebnisse aus spezifischen Bewerben über mehrere Turniere hinweg sammelt und nach einem eigenen Regelwerk auswertet.
**Beispiele:** "Casino Grand Prix 2026", "OÖ Landesmeisterschaft Dressur Allgemeine Klasse".
**Attribute:**
* `Serie-ID` (PK): Eindeutiger technischer Schlüssel (UUID).
* `Name`: Offizieller Name der Serie.
* `Saison`: Das Jahr, in dem die Serie stattfindet.
* `Reglement_Text`: Die spezifischen Regeln für die Wertung (Punktesystem, etc.).
* `Teilnahmeberechtigung_Text`: Regeln, wer an der Serie teilnehmen darf.
* `Qualifikationsbewerbe`: Eine Liste von Verweisen auf die `Bewerb-IDs`, deren Ergebnisse für diese Serie gewertet werden.
---
### 5. Entität: `Akteur`
**Zweck:** Zentrale, widerspruchsfreie Verwaltung aller beteiligten Personen und Organisationen.
**Beispiele:** Ein Reiter, ein Pferdebesitzer, ein Züchter, ein Richter, ein Reitverein.
**Attribute:**
* `Akteur-ID` (PK): Eindeutiger technischer Schlüssel (UUID).
* `Typ`: `PERSON` oder `ORGANISATION`.
* `Name`: Vollständiger Name der Person oder Organisation.
* `Kontakt`: Adress- und Kontaktdaten.
* `Rollen`: Liste der Rollen (z.B. `REITER`, `RICHTER`).
* **OEPS-Daten (für Personen):**
* `Satznummer`: 6-stellig, numerisch (Primärschlüssel OEPS).
* `Lizenz`: Aktueller Lizenzcode (z.B. "R1").
* `Startkarte`: Boolean/Status (Jahresgebühr bezahlt?).
* `Verein_ID`: Verweis auf den Stammverein.
* **Identifikatoren (Sonstige):**
* `FEI-ID`
* `Mitgliedsnummer_Zuchtverband`
---
### 6. Entität: `Pferd`
**Zweck:** Zentrale Verwaltung aller Pferde, egal ob im Sport oder in der Zucht.
**Beispiele:** Ein international erfolgreiches Sportpferd, eine Zuchtstute.
**Attribute:**
* `Pferd-ID` (PK): Eindeutiger technischer Schlüssel (UUID).
* `Name`: Offizieller Name des Pferdes.
* `Abstammung_Vater_ID` (FK): Verweis auf ein anderes `Pferd` (Vater).
* `Abstammung_Mutter_ID` (FK): Verweis auf ein anderes `Pferd` (Mutter).
* `Besitzer_ID` (FK): Verweis auf den `Akteur`, dem das Pferd gehört.
* **OEPS-Daten:**
* `Satznummer`: 10-stellig, numerisch (Primärschlüssel OEPS).
* `Kopfnummer`: 4-stellig, alphanumerisch (Permanente ID).
* `Lebensnummer`: 9-stellig (Zuchtnummer).
* **FEI-Daten:**
* `FEI-ID`: Eindeutige FEI-Nummer.
* `FEI-Pass`: Passnummer (kann abweichen).
@@ -0,0 +1,11 @@
---
type: Reference
status: ACTIVE
owner: Lead Architect
---
# Entitäten des Kern-Modells
Dieses Verzeichnis enthält detaillierte Beschreibungen der zentralen fachlichen Entitäten des "Meldestelle"-Projekts.
Jede Datei beschreibt eine Entität und ihre Attribute.
Diese Dokumente sind die "Wahrheit" für die Implementierung.
@@ -0,0 +1,101 @@
# Prozess: ZNS-Import (Master Data Sync)
**Status:** Draft / Konzept
**Datum:** 17.03.2026
## 1. Ausgangslage & Herausforderungen
Das OEPS stellt die Stammdaten als ZIP-Datei (`zns.zip`) bereit, die in Form von textbasierten ASCII-Dateien (Codepage
850) vorliegen. Die Struktur ist starr, nicht relational und erfahrungsgemäß oft fehlerbehaftet oder unsauber
formatiert (Legacy-Spezifikation V2.4).
Zusätzlich ändern sich Lizenzstände, Sperrlisten oder Registrierungen laufend. Weiters ist die Meldestelle oft
gezwungen, vor Ort manuelle Korrekturen vorzunehmen oder Daten aus anderen Quellen (z.B. Zuchtverbände wie AWÖ) zu
integrieren.
## 2. Architektonische Entscheidung: Event Sourcing & CQRS
Um den Anforderungen (vollständige Historie, turnierspezifischer Datenstand, fehlertoleranter Import, **manuelle
Overrides**) gerecht zu werden, wird der ZNS-Import nach Prinzipien von **Event Sourcing** und **CQRS (Command Query
Responsibility Segregation)** konzipiert.
* Wir überschreiben keine Daten einfach (`UPDATE`), sondern hängen Änderungen als Ereignisse (`EVENTS`) an.
* Dies ermöglicht es uns, den Stand einer Person oder eines Pferdes für die Ewigkeit exakt zu rekonstruieren, selbst
wenn sich die Stammdaten ändern.
### 2.1 Der Import-Ablauf (Die "Command" Seite)
1. **Ingestion:** Der User (Meldestelle) lädt die `zns.zip` hoch oder triggert einen Import aus einer anderen Quelle (
Zuchtverband).
2. **Parsing & Cleansing:** Ein dedizierter Importer-Service entpackt die ZIP, liest die Dateien zeilenweise (Codepage
850!) und konvertiert die starren ASCII-Strings in nutzbare DTOs (Data Transfer Objects). Hier greifen erste
Reinigungs-Routinen.
3. **Event Generation:** Der Service vergleicht die geparsten Daten mit dem aktuellen Stand (der "Read Model"
Datenbank).
* Findet er einen neuen Akteur (Satznummer bisher unbekannt), erzeugt er ein `ActorCreatedEvent`.
* Findet er Änderungen (z.B. Lizenz wurde von R1 auf R2 erhöht, oder Sperre wurde gesetzt), erzeugt er ein
`ActorUpdatedEvent` (bzw. spezifischer `LicenseUpgradedEvent`, `ActorLockedEvent`).
4. **Manuelle Korrekturen (Overrides):** Wenn die Meldestelle vor Ort Daten korrigiert (weil die OEPS-Daten falsch
waren), erzeugt das System ein spezielles Event, z.B. `ManualActorCorrectionEvent`. Dieses Event hat eine **höhere
Priorität** als zukünftige `ActorUpdatedEvents` aus dem ZNS-Import, solange der OEPS die Daten in seinem System nicht
korrigiert hat (Lösung z.B. über einen "Ignorier-Zeitstempel" oder Prioritäts-Flags in der Projektion).
5. **Event Log:** Diese Events werden in einem zentralen Event Log (dem "Event Store") persistiert. Dies ist die
absolute Single Source of Truth.
### 2.2 Die Datenbereitstellung (Die "Query" Seite)
1. **Projection (Projektion):** Kleine "Listener" hören auf das Event Log und bauen daraus die relationale
Lesedatenbank (SQLite / PostgreSQL) auf. Hierbei wird die Logik angewandt, dass manuelle Korrekturen der Meldestelle
Vorrang vor veralteten Verbandsdaten haben.
2. **Turnier-Snapshot:** Wenn ein Turnier konfiguriert wird oder am Vortag aktualisiert wird, zieht sich das System
einen "Snapshot" (Schnappschuss) der aktuellen Stammdaten und verknüpft diese mit der Turnier-ID.
3. **Zuchtverbands-Daten (Fremdformate):** Die Architektur erlaubt es uns leicht, neue Parser (z.B. für AWÖ-Daten) zu
schreiben. Diese lesen die fremden Formate ein und generieren die gleichen Standard-Events (`HorseCreatedEvent`, aber
evtl. mit Lebensnummer statt OEPS-Satznummer), die dann problemlos in die bestehende Projektion einfließen.
## 3. Datenhaltung (Konzeptuelles Modell)
```mermaid
sequenceDiagram
participant User
participant ImporterService as ZNS / AWÖ Importer
participant EventStore as Event Log (Append Only)
participant Projections as DB (Read Models)
User ->> ImporterService: Upload zns.zip (Freitag vor Turnier)
ImporterService ->> ImporterService: Parse & Clean
rect rgb(200, 220, 240)
Note right of ImporterService: Generierung von Import-Events
ImporterService ->> EventStore: Append: ActorUpdatedEvent(Satznummer: 123456, License: R2)
end
User ->> EventStore: Manuelle Korrektur am Turniertag
rect rgb(240, 200, 200)
Note right of User: Generierung von Override-Events
User ->> EventStore: Append: ManualActorCorrectionEvent(Satznummer: 123456, Name: "Neuer Name")
end
EventStore -->> Projections: Update Relational DB (z.B. aktueller Stand)
Note over Projections: Turniersystem liest nur <br/>aus Read Models. Manuelle Korrekturen<br/>gewinnen gegen Import-Daten.
```
## 4. Vorteile dieser Architektur
* **Audit-Sicherheit:** Wir wissen exakt, *wann* sich *was* geändert hat. Nichts geht verloren.
* **Archivierung:** Ein Turnier-Archiv muss nicht mehr mühsam als riesiger PDF/Daten-Dump gesichert werden. Wir können
das Turnier einfach anhand des Timestamps gegen den Event Store abfragen.
* **Fehlertoleranz:** Wenn ein Parsing-Fehler auftritt oder der OEPS kaputte Daten liefert, machen wir einfach ein
Rollback der fehlerhaften Events und projizieren die Datenbank neu. Wir zerschießen nicht die operativen Tabellen.
* **Erweiterbarkeit (Vision):** Wenn später ein "Ergebnis-Analyse-Service" oder ein "Züchter-Portal" angebunden wird,
können diese einfach die historischen Events abonnieren (Kafka/Message Queue).
* **Integration von Drittsystemen (Zuchtverbände):** Fremddaten können durch eigene Parser in unsere Standard-Events
übersetzt und nahtlos integriert werden.
## 5. Zu klärende Details für die Implementierung
* **Sync-Mechanismus:** Wie kommen die Events vom Master-Server auf den Offline-Laptop im Plumpsklo? (Vermutlich eine
robuste Sync-Queue, Kafka könnte für den Offline-Einsatz zu schwergewichtig sein, Alternativen evaluieren).
* **Event-Payload:** Definition der JSON-Struktur für die wichtigsten Events (`zns.actor.updated`,
`zns.horse.registered`).
* **Merge-Logik:** Wie lange bleibt ein `ManualActorCorrectionEvent` gültig, bevor ein zukünftiges Update vom ZNS diesen
Wert wieder überschreiben darf?
+19
View File
@@ -0,0 +1,19 @@
---
type: Reference
status: ACTIVE
owner: Lead Architect
---
# Das Kern-Modell (Core Model)
Dieses Verzeichnis ist die "Single Source of Truth" für das destillierte, fachliche Wissen des Projekts. Nur was hier beschrieben ist, gilt als vereinbarte Wahrheit für die Implementierung.
## Struktur
* `Entities/`: Beschreibt die zentralen fachlichen Entitäten des Systems (z.B. Event, Turnier, Akteur).
* `Processes/`: Dokumentiert die wichtigsten fachlichen Prozesse und Abläufe (z.B. Nennungsprozess, Ergebniserfassung).
* `Rules/`: Definiert explizite Geschäftsregeln und Validierungen.
## Workflow
Informationen in diesem Verzeichnis sind das Ergebnis der Analyse von externen Quellen (siehe `../02_Reference`) und Workshops (siehe `../03_Analysis`).
Jede Änderung am Core Model sollte nachvollziehbar und idealerweise durch ein ADR gestützt sein.