# 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?
