Files
linusandClaude Sonnet 5 03f6fdb0d9 Erstumsetzung: OAMC Turnierauswertung (AP 0–7, 9)
* Datenmodell + Alembic-Initialmigration (Plan §4), Seed Saison 2026
* Wertungs-Engine (Plan §3.3), regelbasiert je Kategorie/Saison
  - Abnahmetest gegen die Ergebnisliste 2026-05-31
* Meisterschaftsberechnung mit Streichresultaten + Tie-Break
  (PLATZHALTER-Punktetabelle, O-1 offen)
* Ingest-API (idempotent, dry_run) inkl. Excel-Leser und Fahrer-Matching/Merge
* Zeitmessungs-Endpoint (Batch, idempotent, unzugeordnet/unplausibel) + SSE-Anzeige
  - Mess-Pi-Client bewusst NICHT enthalten (docs/zeitmessung-endpoint.md)
* Frontend: öffentliche Seiten + internes Backend (Jinja2/HTMX, kein Build)
* Auslieferung: Docker (multi-arch) + docker-compose sowie Debian-Paket/APT-Repo
* 57 Tests grün gegen echte PostgreSQL-Testdatenbank

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-05 18:49:00 +02:00

849 lines
45 KiB
Markdown
Raw Permalink 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.
# Plan: OAMC Turnierauswertung — Webanwendung
**Stand:** 2026-09-05 · **Status:** Entwurf, noch nicht freigegeben
**Änderung 05.09.:** Zeitmessung per Lichtschranke aufgenommen (§3.6, §5.4, §6.2, AP 5)
---
## 1. Ausgangslage
### 1.1 Der Verein und die Sportart
Der **OAMC Reinheim e.V. im ADAC** (Odenwälder Automobil- und Motorsportclub) richtet
**ADAC Motorrad-Turniere** aus. Diese laufen nach der **ADAC-Turnierordnung** im Bereich
des **ADAC Hessen-Thüringen (HTH)**.
Motorrad-Turniersport ist kein Rennsport, sondern ein Geschicklichkeitswettbewerb: Die
Fahrer durchfahren einen Parcours mit festen Aufgaben, dabei zählen **Fehlerpunkte** und
**Fahrzeit**.
Aufgaben im Parcours (laut Sportbeschreibung auf oamc.de):
Fahrzeugabnahme · Schätzen (im Stand und in Fahrt) · Slalom · Acht-Figur · Spurgasse ·
Kreisel · Tor · Schrägbrett · Wippe (mit und ohne Spurbrett) · Gummiringe ·
Dose umsetzen · Fahrgasse · Stop Haltelinie · Langsamfahrstrecke
### 1.2 Aktueller Ist-Zustand der Website
Die bestehende Seite **oamc.de** (Zweitdomain: oamc-reinheim.de) ist eine statische
**HTML-4.01-Frameset-Website**:
- Aufbau: `index``oben.htm` (Navigation) + `hauptfenster.htm`, jede Rubrik wieder
ein eigenes Frameset (`m/frame-m.htm``m1.htm` Menü + `m2.htm` Inhalt)
- Rubriken: Verein · VÜP ADAC-Platz · Kartgruppe · **Motorradgruppe** · Fahrrad JVS ·
Anfahrt · Download · Weblinks · Flohmarkt · BikerSafetyDay
- **Alle Ergebnisse liegen ausschließlich als PDF** unter `/datei/` und werden auf
`d/d-mot-turnier.htm` verlinkt — Tagesergebnisse, Meisterschaftsstände (HTH 20022026),
Bundesturnier-Ergebnisse, Protokolle
- Die Seite **`m/m-fahrer.htm` ist eine „BAUSTELLE"** — es gibt aktuell **keine
Fahrerdatenbank**. Genau das ist die Lücke, die dieses Projekt schließt.
- `m-ergebnisse.htm` und `m-ausschreibung.htm` verweisen nur auf die PDF-Downloadseite
### 1.3 Wie heute ausgewertet wird
Die Rahmenausschreibung 2026 nennt als Anforderung an den Veranstalter:
> „Auswertungscomputer mit MSOffice (**Excel**) und Drucker sollte zeitgemäß vorhanden sein."
Es existieren Vorlagen als `.xlsx` (z. B. `MT-NENNLISTE-5-2025.xlsx`). Die Auswertung
läuft also heute **in Excel**, das Ergebnis wird als PDF exportiert und manuell auf die
Website gelegt. Meisterschaftsstände werden separat fortgeschrieben.
**Schmerzpunkte:**
| Problem | Auswirkung |
|---|---|
| Ergebnisse nur als PDF | nicht durchsuchbar, nicht verlinkbar, nicht mobil lesbar |
| Meisterschaftsstand manuell | fehleranfällig, spät verfügbar |
| Keine Fahrerhistorie | „Wie oft war X schon dabei?" nicht beantwortbar |
| Fahrerstammdaten pro Turnier neu erfasst | Tippfehler, Dubletten, Namensvarianten |
| Frameset-Website | kein Deep-Link, schlechtes SEO, unbrauchbar auf dem Handy |
---
## 2. Ziel
Eine Webanwendung, die
1. **Turnierergebnisse auswertet** — Fehlerpunkte + Zeit → Platzierung je Wertungsklasse
2. **Alle Daten in einer Datenbank hält** statt in Excel und PDF
3. **Zeiten per API von der Lichtschranken-Zeitmessung entgegennimmt** (zweiter Raspberry
Pi am Parcours, siehe §3.6), in die Datenbank schreibt und **live anzeigt**
4. **Fahrer samt Stammdaten und Historie** bereitstellt (die heutige „Baustelle")
5. Die **Meisterschafts-/Pokalwertung** automatisch fortschreibt
### Nicht-Ziele (bewusst außerhalb dieses Projekts)
- Ablösung der kompletten oamc.de (Kart, Fahrrad, Flohmarkt, VÜP bleiben, wo sie sind)
- Online-Nennung/Anmeldung durch Teilnehmer → **Phase 2**, siehe §9
- Elektronische Erfassung der **Fehlerpunkte** — die bleiben beim Wertungspersonal im
Parcours und werden manuell eingegeben (siehe §3.6)
- Zahlungsabwicklung des Nenngelds (10 € je Start, wird vor Ort kassiert)
---
## 3. Fachliche Grundlagen
> ⚠️ Die folgenden Regeln wurden aus der Ergebnisliste
> `2026-05-31-MT-REIN-HAIN.PDF` und der `MT-Rahmenausschreibung-2026.pdf`
> **abgeleitet**. Sie sind **vor der Implementierung gegen die ADAC-Turnierordnung 2026
> (`MT-D2025.pdf`) und mit dem Bereichsleiter zu verifizieren** — siehe §10.
### 3.1 Veranstaltungsstruktur
- Ein Renntag kann ein **Doppelturnier** sein: zwei Turniere zweier Ortsclubs laufen
parallel auf einem Platz, z. B. „ADAC-Motorrad-Doppelturnier des MSC Hainstadt und
des OAMC Reinheim".
- **Jeder Fahrer wird in beiden Turnieren getrennt gewertet** — die Ergebnisliste vom
31.05.2026 enthält für dieselben Fahrer je einen Block „Reinheim" und einen Block
„Hainstadt" mit **eigenen Zeiten, Fehlern und Platzierungen**.
- ⇒ Datenmodell-Konsequenz: `veranstaltung` (Renntag) : `turnier` (Wertungslauf) = 1:n
### 3.2 Klassen
**Jugendklassen** (Einteilung strikt nach Jahrgang, Ausweispflicht bei der Nennung):
| Klasse | Jahrgang | ~Alter | Fahrzeug |
|---|---|---|---|
| J 1 | 2019 / 2018 / 2017 | 79 | Kindermotorrad max. 110 ccm / 5,5 kW |
| J 2 | 2016 / 2015 | 1011 | Kindermotorrad max. 110 ccm / 5,5 kW |
| J 3 | 2014 / 2013 | 1213 | Mofa, Motorrad + Roller max. 125 ccm / 11 kW |
| J 4 | 2012 / 2011 | 1415 | Mofa, Motorrad + Roller max. 125 ccm / 11 kW |
| J 5 | 2010 / 2009 / 2008 (bis 18. Geburtstag) | 1618 | Mofa, Motorrad + Roller max. 125 ccm / 11 kW |
Für Klasse 1 muss der Teilnehmer **am Veranstaltungstag** bereits 7 Jahre alt sein.
⇒ Die Jahrgangstabelle ist **jahresabhängig** und muss versioniert in der DB liegen,
nicht im Code (`klassen_jahrgang` mit `saison`).
**Erwachsenenklassen:** `S 1``S 9` (Stammfahrer) und `A 1``A 4` (Anfänger), unterteilt
nach Fahrzeugtyp/Hubraum. Aus der Sportbeschreibung: Mokicks und Roller bis 80 ccm ·
bis 250 ccm · bis 650 ccm · über 650 ccm · Enduro (mehrere Hubraumklassen) ·
Seitenwagen · **Pedelec / E-Bike** · **E-Scooter** (neu).
> ❗ Die genaue Zuordnung `S 1`…`S 9` / `A 1`…`A 4` → Fahrzeugtyp ist aus den
> öffentlichen Seiten **nicht** ersichtlich. Muss erfragt werden (§10).
**Wertungsklasse ≠ Klasse:** In der Ergebnisliste werden Klassen zu Wertungsklassen
zusammengefasst, wenn zu wenige Starter da sind — z. B. eine gemeinsame Wertung
„S 5, S 9, S 3, S 6, S 7" und „A 1, A 4". Das Zusammenlegen ist eine
**Entscheidung pro Turnier** und gehört als Datum in die DB, nicht als Regel in den Code.
### 3.3 Wertungslogik ← Kernstück der Anwendung
Spalten der Ergebnisliste:
`Start-Nr.` · `Voller Name` · `Regionalclub` · `Klasse` · `Geschlecht` · `Kategorie` ·
`Summe Zeit` · `Summe Fehler` · `Gesamt` · `Platzierung`
**Es gibt zwei verschiedene Berechnungen für `Gesamt`:**
**a) Erwachsene (Kategorie S, und A-Fahrer):**
```
Gesamt = Summe Zeit + Summe Fehler
Platzierung = aufsteigend nach Gesamt
```
Belege aus der Liste vom 31.05.2026, Wertungsklasse „S 5, S 9, S 3, S 6, S 7":
| Fahrer | Klasse | Zeit | Fehler | Gesamt | Platz |
|---|---|---|---|---|---|
| Bernius Meik | S 6 | 105,43 | 0 | **105,43** | 1 |
| Schmunk Christian | S 9 | 103,26 | 10 | **113,26** | 2 |
| Krack Robin | S 5 | 130,33 | 0 | **130,33** | 4 |
| Steinbrech Nicolai | S 5 | 65,71 | 1210 | **1275,71** | 11 |
**b) Jugend (Kategorie J):**
```
Gesamt = Summe Fehler ← die Zeit geht NICHT in die Wertung ein
Platzierung = aufsteigend nach Fehler, bei Gleichstand aufsteigend nach Zeit
```
Das deckt sich mit der Sportbeschreibung:
> „Bei den Turnieren kommt es in der Jugendklasse vorrangig auf die fehlerfreie
> Durchfahrt an, da die Zeit erst bei Punktgleichheit herangezogen wird."
Belege, Wertungsklasse J 3:
| Fahrer | Zeit | Fehler | Gesamt | Platz | Anmerkung |
|---|---|---|---|---|---|
| Freund Philipp | 136,88 | 0 | **0** | 1 | drei Fahrer mit 0 Fehlern … |
| D'Angelo Leandro | 140,40 | 0 | **0** | 2 | … Reihenfolge über die Zeit |
| Heil Luca | 144,46 | 0 | **0** | 3 | |
| Mattes Flynn | 160,05 | 10 | **10** | 4 | |
| Ratter Mitja | 148,42 | 60 | **60** | 7 | Gleichstand 60 Fehler … |
| Kemesies Finlay | 165,39 | 60 | **60** | 8 | … 148,42 < 165,39 |
⇒ Die Wertungsformel ist **pro Kategorie konfigurierbar** zu implementieren, nicht
hart zu verdrahten (`wertungsregel` als Strategie/Enum je Kategorie und Saison).
**Fehlerpunkte** treten in Vielfachen von 10 auf (10/20/30/…/1210). Die Zuordnung
„welcher Fehler an welcher Aufgabe kostet wie viele Punkte" steht in der
ADAC-Turnierordnung und ist zu beschaffen (§10).
**Mehrfachstarts:** Ein Fahrer kann am selben Tag **mehrfach starten** (anderes Fahrzeug,
andere Klasse). Beispiel 31.05.2026: *Schmunk Christian* startet in S 9 **und** S 3,
*Bernius Meik* in S 6 **und** S 7 — jeweils mit eigener Startnummer und eigenem Ergebnis.
⇒ Der Schlüssel eines Ergebnisses ist **nicht** (Turnier, Fahrer), sondern
(Turnier, Start), und Nenngeld fällt „für jeden Start" an.
### 3.4 Meisterschafts-/Pokalwertung (HTH)
- **Erwachsene:** ein Gesamtklassement der A- und S-Fahrer/innen (klassenübergreifend)
- **Jugend:** Pokalwertung **klassenweise** J 1 J 5
**Streichresultate** — nicht alle Läufe zählen:
| Turniere in der Saison | 12 | 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 |
|---|---|---|---|---|---|---|---|---|---|
| **davon gewertet** | 8 | 8 | 8 | 7 | 6 | 5 | 4 | 3 | 2 |
Nicht jede Veranstaltung zählt zur Meisterschaft — der Terminkalender markiert Läufe
„mit Meisterschaftswertung" bzw. „o. M. = ohne Meisterschaft".
**Endlaufqualifikation:** die besten **10 Erwachsenen** und **10 Jugendlichen** der
HTH-Meisterschaft (Jugend ohne Trennung Damen/Herren, Verteilung prozentual nach
Teilnehmerzahl der 5 Jugendklassen). **Bei Punktgleichheit zählt die Majorität der
Siege.** Endgültige Festlegung durch den Bereichsleiter ⇒ die Anwendung *schlägt vor*,
sie *entscheidet nicht*.
> ❗ Die **Punktetabelle** (Platz → Meisterschaftspunkte) ist noch nicht bekannt (§10).
### 3.5 Weitere Regeln aus der Rahmenausschreibung 2026
- Nenngeld einheitlich **10 €** je Start (S, A, Jugend, Mofa/Moped, E-Bike, Pedelec, E-Scooter)
- **Nennschluss:** Klasse S + Jugendklassen 14:00 Uhr, A-Klasse + E-Bike/Pedelec/E-Scooter 15:00 Uhr
- Pokalläufe bei Doppelveranstaltungen beginnen generell 09:00 Uhr
- Gültiger Jugendausweis + unterschriebener Haftungsausschluss sind bei der Nennung vorzulegen
- Eine **nummerierte Nennungsliste** ist zu führen, die Reihenfolge nach Möglichkeit einzuhalten
- Für A-Klassen-Fahrer und Gelegenheitsfahrer wird z. B. jede 10. Startnummer freigehalten
- Schlussbericht + Ergebnisliste müssen nach FFM gemeldet werden — **der Veranstalter-Zuschuss
wird erst überwiesen, wenn beide Dokumente dort sind** ⇒ Export in genau diesen Formaten
ist kein „nice to have", sondern zahlungsrelevant
### 3.6 Zeitmessung per Lichtschranke *(Vorgabe des Auftraggebers)*
Die API wird von einem **zweiten Raspberry Pi am Parcours** bespielt. Dieser ist an eine
**Lichtschranke** angeschlossen, misst die Fahrzeiten, sendet sie an die Anwendung und
**zeigt die Daten des aktuellen Fahrzeugs/Starters an**.
Damit gibt es **zwei Datenquellen pro Ergebnis**:
| Wert | Quelle | Weg |
|---|---|---|
| `Summe Zeit` | **Lichtschranke** (Messgerät) | automatisch per API |
| `Summe Fehler` | **Wertungspersonal im Parcours** | manuell im internen Frontend |
Ein Ergebnis ist also erst vollständig, wenn **beide** Seiten geliefert haben. Das
Datenmodell muss einen Start mit Zeit, aber ohne Fehlerpunkte (und umgekehrt) sauber
abbilden können — ein „halbes" Ergebnis ist der Normalzustand während der Veranstaltung,
kein Fehler.
#### Konsequenzen für den Entwurf
**a) Rohmessungen statt Summen speichern.**
Die Spalte heißt in der Ergebnisliste `Summe Zeit` — die Zeit ist also vermutlich eine
**Summe mehrerer Messungen** (mehrere Durchgänge oder mehrere gemessene Abschnitte,
z. B. Langsamfahrstrecke und Gesamtdurchfahrt). Die Anwendung speichert deshalb **jede
Einzelmessung** (`zeitmessung`) und bildet die Summe selbst. Rückweg gibt es nicht:
aus einer gespeicherten Summe lassen sich Einzelmessungen nie wieder herstellen, aus
Einzelmessungen die Summe jederzeit. → offene Frage **O-13**
**b) Das Messgerät ist ein eigenständiger API-Client, kein Teil der Anwendung.**
Es bekommt einen **Geräte-Token** (nicht den Auswerter-Login), darf nur Messungen
schreiben und Starter-Daten lesen, und wird in der DB als `geraet` geführt. Ein Gerät
am offenen Platz muss man notfalls einzeln sperren können.
**c) Netzausfall ist der Normalfall, nicht die Ausnahme.**
Am ADAC-Platz kann das Netz wegbrechen. Der Mess-Pi **muss lokal puffern** (SQLite oder
Datei-Queue) und nach Wiederverbindung nachliefern. Deshalb gilt zwingend:
- jede Messung bekommt eine **UUID vom Messgerät** → Server ist idempotent, doppelt
gesendete Messungen erzeugen keine Dubletten
- die Messung trägt **zwei Zeitstempel**: Wanduhr (`gemessen_am`) und **monotone
Gerätezeit** (`monotonic_ns`). Die Wanduhr eines Pi ohne Netz und ohne RTC springt
beim NTP-Sync — die Dauer zwischen zwei Lichtschranken-Ereignissen darf davon nicht
abhängen. **Der Pi berechnet die Dauer selbst und sendet sie**, der Server rechnet
keine Differenz aus zwei Wanduhr-Zeitstempeln.
- der Server nimmt Messungen **rückwirkend** an (`gemessen_am` in der Vergangenheit)
und markiert sie als nachgeliefert
**d) Zuordnung Messung → Starter ist der kritische Punkt.**
Eine Lichtschranke misst eine Zeit, sie weiß nicht, **wer** durchgefahren ist. Die
Zuordnung muss irgendwo herkommen — Eingabe der Startnummer am Mess-Pi, eine
Warteschlange „nächster Starter" aus der Nennliste, oder ein Transponder/RFID.
→ offene Frage **O-14**. Bis zur Klärung wird konservativ geplant:
- der Mess-Pi holt sich per API die **Startreihenfolge** des laufenden Turniers
- er sendet die Messung **mit** der Startnummer, die er gerade anzeigt
- eine Messung **ohne** zuordenbare Startnummer wird nicht verworfen, sondern als
`zeitmessung` ohne `start_id` gespeichert und im Frontend zur manuellen Zuordnung
angeboten. Eine gemessene Zeit ist nicht reproduzierbar — sie darf nie verloren gehen,
nur weil die Zuordnung fehlte.
**e) Fehlmessungen sind Betriebsrealität.**
Zuschauer, Helfer oder ein zurücksetzendes Motorrad lösen die Lichtschranke aus.
Nötig sind: **Entprellung/Mindestabstand** zwischen zwei Triggern auf dem Pi, ein
**Verwerfen-Knopf** in der Erfassung, sowie Plausibilitätsprüfung serverseitig
(eine Fahrzeit von 3 s oder 900 s ist zu markieren, nicht stillschweigend zu übernehmen).
Jede verworfene Messung bleibt mit Grund gespeichert.
**f) Die Anzeige am Parcours ist ein eigener Anwendungsfall.**
Der Mess-Pi zeigt Starter- und Fahrzeugdaten an, zieht sie also **lesend** aus der API.
Das braucht einen schlanken, schnellen Endpunkt und eine Live-Aktualisierung (SSE),
und er muss **auch bei Netzausfall die zuletzt geladene Startliste weiter anzeigen**.
> ❗ Was genau angezeigt werden soll (Startnummer, Name, Klasse, Fahrzeug, laufende Zeit,
> letzte Zeit?) und auf welcher Hardware (HDMI-Monitor, kleines Display, LED-Tafel) ist
> noch offen → **O-15**.
---
## 4. Datenmodell
```
verein ──┐
├──< fahrer ──< start >── turnier >── veranstaltung
saison ──┤ │ │ │
├──< klasse ───┘ │ └──< wertungsklasse
│ ├──< ergebnis (Zeit + Fehler, berechnet)
│ └──< zeitmessung >── geraet (Lichtschranke)
└──< meisterschaft ──< meisterschaft_stand
```
### Tabellen
**`verein`** — Regionalclub / Ortsclub
`id` · `name` („OAMC Reinheim", „MSC Schotten", „PMS Kassel", „Hainstadt") ·
`kurzname` · `adac_gau` · `aktiv`
**`fahrer`** — Stammdaten, **einmalig**, nicht pro Turnier
`id` · `nachname` · `vorname` · `geburtsjahr` · `geburtsdatum` (nullable, für J1-Prüfung
„am Veranstaltungstag 7 Jahre") · `geschlecht` (M/W/D) · `verein_id` ·
`adac_mitgliedsnr` (nullable) · `jugendausweis_gueltig_bis` · `aktiv` · `notiz` ·
`erstellt_am` / `geaendert_am`
**Dublettenerkennung** beim Import nötig, siehe §6.3
**`saison`** — Jahr, macht Regeln versionierbar
`jahr` (PK) · `turnierordnung_version` · `streichresultate_tabelle` (JSONB) ·
`nenngeld_cent` · `aktiv`
**`klasse`** — Klassendefinition **pro Saison**
`id` · `saison_jahr` · `code` („J 1", „S 6", „A 4") · `kategorie` (J/S/A) ·
`bezeichnung` · `jahrgaenge` (int[], nur Jugend) · `ccm_max` · `kw_max` ·
`fahrzeugtyp` · `sortierung`
**`veranstaltung`** — der Renntag
`id` · `datum` · `titel` · `ort` · `ausrichter_verein_id` · `ist_doppelturnier` ·
`veranstaltungsleiter` · `turnierleiter` · `status` (geplant/laufend/vorlaeufig/final)
**`turnier`** — der einzelne Wertungslauf (bei Doppelturnier zwei pro Veranstaltung)
`id` · `veranstaltung_id` · `name` („Reinheim" / „Hainstadt") · `veranstalter_verein_id` ·
`zaehlt_meisterschaft` (bool) · `status`
**`wertungsklasse`** — pro Turnier zusammengefasste Klassen
`id` · `turnier_id` · `bezeichnung` („S 5, S 9, S 3, S 6, S 7") ·
`klasse_ids` (int[]) · `sortierung`
**`start`** — eine Teilnahme; ein Fahrer kann mehrere pro Turnier haben
`id` · `turnier_id` · `fahrer_id` · `klasse_id` · `wertungsklasse_id` · `startnummer` ·
`fahrzeug` (Text) · `nenngeld_bezahlt` · `status` (genannt/gestartet/DNF/DNS/DSQ)
→ UNIQUE (`turnier_id`, `startnummer`)
**`ergebnis`** — 1:1 zum Start, getrennt für saubere Nachträge/Korrekturen
`start_id` (PK) · `summe_zeit` (numeric(7,2), **aus `zeitmessung` summiert**) ·
`summe_fehler` (int, **manuell erfasst**) · `gesamt` (numeric(9,2), **berechnet**) ·
`platzierung` (int, **berechnet**) · `zeit_vollstaendig` (bool) ·
`fehler_vollstaendig` (bool) · `erfasst_von` · `erfasst_am` · `korrigiert_am` ·
`korrektur_grund`
→ Die beiden `*_vollstaendig`-Flags machen sichtbar, welche der zwei Quellen (§3.6)
noch fehlt. Gewertet wird erst, wenn beide gesetzt sind.
**`geraet`** — Messgerät / Anzeige am Parcours
`id` · `bezeichnung` („Lichtschranke Ziel", „Anzeige Start") · `typ`
(lichtschranke/anzeige) · `token_hash` · `aktiv` · `letzte_meldung_am` ·
`firmware_version` · `notiz`
`letzte_meldung_am` ist der Heartbeat: das Frontend zeigt, ob die Messung noch lebt
**`zeitmessung`** — Einzelmessung, **nicht** die Summe
`id` (UUID, **vom Gerät vergeben** → Idempotenz) · `geraet_id` · `turnier_id` ·
`start_id` (**nullable** — unzugeordnete Messungen gehen nicht verloren) ·
`startnummer_gemeldet` (int, was das Gerät angezeigt hat) · `messpunkt`
(z. B. „Durchgang 1", „Langsamfahrstrecke") · `dauer_sekunden` (numeric(7,2),
**vom Gerät berechnet**) · `gemessen_am` (timestamptz, Wanduhr) · `monotonic_ns` (bigint) ·
`empfangen_am` · `nachgeliefert` (bool) · `status` (gueltig/verworfen/unzugeordnet) ·
`verworfen_grund` · `roh` (JSONB, Rohereignisse der Lichtschranke)
→ UNIQUE (`id`); Index auf (`turnier_id`, `status`), (`start_id`)
**`aufgabe`** / **`ergebnis_aufgabe`** — *optional, Phase 2*: Einzelwertung je Parcours-Aufgabe
(Slalom, Wippe, Kreisel …). Die heutigen PDFs enthalten nur Summen; die Struktur
vorzusehen kostet jetzt nichts und erlaubt später Detailauswertungen. Sobald die
Lichtschranke mehrere Messpunkte bedient, wächst `zeitmessung.messpunkt` natürlich in
diese Struktur hinein.
**`meisterschaft`** — z. B. „HTH Erwachsene 2026", „HTH Jugend J 3 2026"
`id` · `saison_jahr` · `bezeichnung` · `kategorie` · `klasse_id` (nullable) · `regelwerk` (JSONB)
**`meisterschaft_stand`** — materialisiert, nach jedem Turnier neu berechnet
`meisterschaft_id` · `fahrer_id` · `punkte_gesamt` · `punkte_je_lauf` (JSONB) ·
`gestrichene_laeufe` (JSONB) · `anzahl_siege` (Tie-Break!) · `platz` · `berechnet_am`
**`import_job`** — Audit-Trail jedes API-/Datei-Imports
`id` · `quelle` · `dateiname` · `roh_payload` (JSONB) · `status` · `fehler` ·
`angelegt_von` · `angelegt_am`
**`benutzer`** / **`rolle`** — Admin, Auswerter, Lesend
### Kernprinzipien
- `gesamt` und `platzierung` werden **von der Anwendung berechnet**, nicht importiert —
importierte Werte werden gegen die Berechnung **geprüft** und Abweichungen gemeldet
(fängt Excel-Fehler in Altdaten und Regelmissverständnisse auf beiden Seiten)
- Alles **saisonversioniert**: Klassen, Jahrgänge, Streichresultate, Punktetabelle.
Ergebnisse aus 2019 müssen 2030 noch korrekt reproduzierbar sein.
- Ergebnisse sind **append-only mit Korrektur-Historie**, kein stilles UPDATE
---
## 5. Architektur & Technologie
### 5.1 Vorhandene Umgebung (geprüft auf diesem Gerät)
| | |
|---|---|
| Hardware | Raspberry Pi 4 Model B Rev 1.5, 8 GB RAM, 117 GB Speicher (94 GB frei) |
| OS | Debian 13 (trixie), Linux 6.18 aarch64 |
| **PostgreSQL 17.10** | installiert **und aktiv** ✅ |
| **nginx** | installiert **und aktiv** ✅ |
| **Python 3.13.5** + pip 25.1.1 | ✅ |
| Node.js / npm | ❌ nicht installiert |
| Docker | ❌ nicht installiert |
| git | ❌ **nicht installiert** — vor Projektstart nachinstallieren |
### 5.2 Empfohlener Stack
Der Stack folgt dem, was **schon läuft** — auf einem Pi 4 zählt jedes vermiedene
Laufzeit-Ökosystem.
| Schicht | Wahl | Begründung |
|---|---|---|
| Datenbank | **PostgreSQL 17** | läuft bereits; JSONB für Regelwerke, saubere Constraints, echte Transaktionen |
| Backend | **Python 3.13 + FastAPI** | Python ist da; FastAPI liefert die API-Doku (OpenAPI/Swagger) automatisch mit — wichtig, weil Dritte die API bespielen sollen |
| ORM / Migration | **SQLAlchemy 2 + Alembic** | Schemaänderungen über Jahre nachvollziehbar |
| Validierung | **Pydantic v2** | ein Schema für API-Validierung *und* Doku |
| Templates | **Jinja2**, serverseitig gerendert | Ergebnislisten sind Dokumente, keine App. Kein Build-Schritt, kein Node, sofort suchmaschinenlesbar |
| Interaktivität | **HTMX** (~14 kB, per CDN oder lokal) | Filter/Sortierung/Live-Nachladen ohne SPA-Apparat |
| Live-Updates | **SSE** (Server-Sent Events), von FastAPI direkt unterstützt | Anzeige am Parcours und Live-Ergebnisse; einseitiger Datenfluss, kein WebSocket-Zustand nötig, reconnectet von selbst |
| CSS | handgeschrieben oder **Pico.css** | keine Build-Pipeline |
| Excel/CSV-Import | **openpyxl** + `csv` | die Nennlisten sind `.xlsx` |
| PDF-Export | **WeasyPrint** | Schlussbericht + Ergebnisliste müssen nach FFM |
| Server | **uvicorn** hinter dem vorhandenen **nginx** | nginx macht TLS und liefert Statisches |
| Prozess | **systemd-Unit** | kein Docker nötig |
| TLS | **Let's Encrypt / certbot** | |
**Bewusst nicht gewählt:** React/Vue-SPA (Build-Kette auf dem Pi, kein SEO, viel Aufwand
für Tabellenanzeige) · Docker (Overhead ohne Nutzen bei einer App) · SQLite
(Mehrbenutzerbetrieb am Turniertag, Postgres ist schon da)
### 5.3 Betrieb
```
╔═══ Am Parcours ═══════════════╗
║ Pi #2 „Mess-Pi" ║
║ Lichtschranke → GPIO ║
║ lokale Queue (SQLite) ║──┐ POST Messungen (idempotent, gepuffert)
║ Anzeige Starter/Fahrzeug ║<─┼─ GET Startliste + SSE Live
╚═══════════════════════════════╝ │
Internet / LAN ──> nginx (TLS, statisch) ┘
└→ uvicorn/FastAPI (systemd) → PostgreSQL 17 (lokal)
→ /var/oamc/uploads (PDFs, Bilder)
```
- **Backup:** täglicher `pg_dump` + wöchentliche Vollsicherung auf externes Medium.
Auf einer SD-Karte/SSD im Pi ist das nicht optional.
- **Offline-Fähigkeit am Turniertag** ist durch die Zeitmessung **Pflicht geworden**, nicht
mehr optional (§3.6c). Zwei Ebenen:
1. der **Mess-Pi** puffert lokal und liefert nach — deckt kurze Aussetzer ab
2. bei Veranstaltungen **ohne Internet** muss der Anwendungs-Pi **mit vor Ort** und
beide Geräte hängen an einem lokalen Netz (Router/Hotspot ohne Uplink). Die
Synchronisation zum öffentlichen Server erfolgt danach.
⇒ Entscheidung dazu offen, siehe **O-7**.
- **Zeitserver:** ohne Internet gibt es kein NTP. Der Anwendungs-Pi sollte im lokalen
Netz selbst NTP anbieten, damit die Zeitstempel zusammenpassen. Für die *Messung*
ist das unkritisch (der Mess-Pi rechnet monoton, §3.6c), für die Protokollierung nicht.
### 5.4 Software auf dem Mess-Pi
Bewusst klein und eigenständig — das Gerät muss auch dann sinnvoll weiterlaufen, wenn
der Server nicht erreichbar ist.
| Teil | Wahl |
|---|---|
| GPIO / Lichtschranke | `gpiozero` mit `pigpio`- oder `lgpio`-Backend; Trigger als **Interrupt**, nicht Polling |
| Zeitmessung | `time.monotonic_ns()`, Entprellung/Mindestabstand konfigurierbar |
| Lokale Queue | SQLite, Einträge bleiben bis vom Server quittiert |
| Versand | HTTP + Geräte-Token, Retry mit Backoff, Batch-Nachlieferung |
| Anzeige | Browser im Kiosk-Modus auf die Anzeige-Seite der Anwendung (§7), Daten per SSE, letzte Startliste im `localStorage` als Offline-Rückfall |
| Betrieb | systemd-Unit, `WatchdogSec`, Autostart, read-only Root-FS erwägen (Stromausfall am Platz) |
> Für saubere Zeiten unter Last ist ein Interrupt-basierter Trigger Pflicht. Ob die
> Genauigkeit eines Linux-Userspace-Prozesses reicht, hängt von der geforderten
> Auflösung ab → **O-16**. Die Ergebnislisten führen **1/100 s** (z. B. `159,16`);
> das ist mit `pigpio` gut erreichbar, mit Polling in Python nicht zuverlässig.
---
## 6. API
Zwei Richtungen: **Ingest** (Daten kommen rein) und **Read** (Daten gehen raus).
### 6.1 Ingest — `POST /api/v1/…` (authentifiziert, API-Key)
| Endpunkt | Zweck |
|---|---|
| `POST /api/v1/veranstaltungen` | Renntag anlegen |
| `POST /api/v1/turniere` | Wertungslauf zum Renntag |
| `POST /api/v1/fahrer` · `PUT /api/v1/fahrer/{id}` | Stammdaten |
| `POST /api/v1/turniere/{id}/starts` | Nennliste (Bulk) |
| `POST /api/v1/turniere/{id}/ergebnisse` | **Ergebnis-Bulk-Upload** ← Kernfall |
| `POST /api/v1/import/xlsx` | Excel-Nennliste/Ergebnisliste als Datei |
| `POST /api/v1/turniere/{id}/finalisieren` | Wertung rechnen, Meisterschaft fortschreiben |
Beispiel-Payload Ergebnis-Upload:
```json
{
"quelle": "excel-auswertung-v3",
"idempotenz_schluessel": "2026-05-31-reinheim-final",
"ergebnisse": [
{ "startnummer": 32, "fahrer": { "nachname": "Gál-Szász", "vorname": "Péter",
"geburtsjahr": 2015, "geschlecht": "M", "verein": "OAMC Reinheim" },
"klasse": "J 2", "summe_zeit": 159.16, "summe_fehler": 10,
"gesamt_erwartet": 10, "platzierung_erwartet": 1 }
]
}
```
**Regeln für den Ingest:**
- **Idempotent** über `idempotenz_schluessel` — ein doppelt gesendeter Upload legt keine
Dubletten an. Am Turniertag mit wackligem Netz ist das Pflicht, nicht Komfort.
- **Zweistufig:** `?dry_run=true` liefert eine Validierungsvorschau (welche Fahrer sind
neu, welche werden gematcht, welche Werte weichen ab) — erst danach wird geschrieben.
- **`gesamt_erwartet` / `platzierung_erwartet`** sind optional; wenn geliefert, rechnet
der Server nach und meldet Abweichungen als Warnung, statt sie zu übernehmen.
- Jeder Aufruf landet in `import_job` mit Roh-Payload — bei Streit über eine Platzierung
ist rekonstruierbar, was wann ankam.
### 6.2 Geräte-API — Zeitmessung & Anzeige *(Mess-Pi, Token-Auth)*
Eigener Namensraum mit eigenem Rechteprofil: schreiben **nur** Messungen, lesen **nur**
das, was die Anzeige braucht. Kompromittierung des Geräts am offenen Platz darf keine
Ergebnisse manipulieren können.
| Endpunkt | Zweck |
|---|---|
| `POST /api/v1/geraete/heartbeat` | „ich lebe" + Firmware/Queue-Länge → Statusanzeige im Frontend |
| `GET /api/v1/geraete/konfiguration` | Entprellzeit, Messpunkte, aktives Turnier — zentral gepflegt statt auf dem Pi |
| `GET /api/v1/turniere/aktiv/startliste` | Startreihenfolge für die Anzeige, ETag-fähig, offlinefest zwischenspeicherbar |
| `GET /api/v1/turniere/aktiv/anzeige` (SSE) | Live-Stream: aktueller Starter, letzte Zeit |
| `POST /api/v1/zeitmessungen` | **Messungen (Batch, idempotent)** ← Kernfall |
| `POST /api/v1/zeitmessungen/{id}/verwerfen` | Fehlmessung markieren |
Beispiel-Payload der Zeitmessung — bewusst als **Batch**, weil der Pi nach einem
Netzausfall mehrere Messungen auf einmal nachliefert:
```json
{
"geraet": "lichtschranke-ziel-01",
"messungen": [
{
"id": "9f2c1e7a-4d38-4b6e-9a11-5c0b2d7e8f30",
"turnier_id": 42,
"startnummer_gemeldet": 32,
"messpunkt": "durchgang-1",
"dauer_sekunden": 159.16,
"gemessen_am": "2026-05-31T10:14:02.482+02:00",
"monotonic_ns": 884213771004,
"roh": { "trigger_start_ns": 884054611004, "trigger_ziel_ns": 884213771004 }
}
]
}
```
Antwort quittiert **jede Messung einzeln** (`angenommen` / `duplikat` / `unzugeordnet` /
`unplausibel`) — erst eine Quittung löscht den Eintrag aus der lokalen Queue des Pi.
**Festlegungen:**
- Die **`id` vergibt das Gerät** (UUIDv4/v7). Nur so ist Nachliefern nach Timeout ohne
Dubletten möglich — der Pi weiß nach einem abgebrochenen Request nicht, ob der Server
ihn verarbeitet hat, und darf ihn deshalb gefahrlos wiederholen.
- Die **Dauer kommt fertig vom Gerät**, der Server rechnet keine Differenz aus Wanduhr-
Zeitstempeln (Begründung §3.6c).
- `startnummer_gemeldet` ist ein **Hinweis, kein Fremdschlüssel**. Der Server löst sie
gegen die Startliste auf; scheitert das, wird die Messung als `unzugeordnet`
gespeichert und im Frontend zur Zuordnung angeboten — **nie verworfen**.
- Plausibilitätsgrenzen (min/max Fahrzeit je Klasse) sind konfigurierbar; Verstöße
werden **markiert**, nicht abgelehnt.
- Der Geräte-Token ist **nicht** der Auswerter-Login und einzeln sperrbar.
### 6.3 Read — `GET /api/v1/…` (öffentlich, ohne Key)
```
GET /api/v1/veranstaltungen?saison=2026
GET /api/v1/turniere/{id}/ergebnisse?wertungsklasse=J3
GET /api/v1/fahrer?q=bernius&verein=OAMC+Reinheim
GET /api/v1/fahrer/{id}/historie
GET /api/v1/meisterschaften/{id}/stand
GET /api/v1/meisterschaften/{id}/endlauf-qualifikation
```
Zusätzlich **iCal** (`/termine.ics`) für den Terminkalender und **CSV-Export** je Liste.
### 6.4 Fahrer-Matching beim Import
Der heikelste Teil des Imports. Aus den Altdaten sichtbar: Namen mit Sonderzeichen
(*Gál-Szász*), Vereinsangaben mal als „OAMC Reinheim", mal als „Hainstadt" (Ortsname
statt Clubname), Namensgleichheiten in Familien (*Gál-Szász Péter* / *Máté* / *János*;
*Bernius Meik* / *Jörg*; *Krack Robin* / *Luca*; *Tinz Xenia* / *Enrique*).
Vorgehen:
1. Exakter Match auf (Nachname, Vorname, Geburtsjahr) — normalisiert (Unicode NFKD,
Kleinschreibung, Bindestriche)
2. Unscharfer Match (Levenshtein / `pg_trgm`) → **Vorschlag**, nie automatisch
3. Kein Match → neuer Fahrer wird angelegt, aber im Import-Report als „NEU" markiert
4. **Merge-Funktion** im Admin, um nachträglich erkannte Dubletten zusammenzuführen
---
## 7. Seiten (Frontend)
**Öffentlich:**
| Seite | Inhalt |
|---|---|
| Startseite Motorrad-Turnier | nächster Termin, letzte Ergebnisse, aktueller Meisterschaftsstand |
| **Termine** | Saisonkalender, mit/ohne Meisterschaftswertung, Ausrichter, iCal-Abo |
| **Ergebnisse** | je Veranstaltung → Turnier → Wertungsklasse, sortierbar, mobil lesbar |
| **Live** (Turniertag) | vorläufiger Stand, sobald Zeiten eintreffen — deutlich als *vorläufig* gekennzeichnet, solange Fehlerpunkte fehlen (§3.6) |
| **Fahrer** | durchsuchbare Liste (löst die „BAUSTELLE" ab) |
| **Fahrerprofil** | Stammdaten, alle Starts, Platzierungen, Bestzeiten, Saisonverlauf |
| **Meisterschaft** | HTH-Stand Erwachsene + Jugend J 1J 5, inkl. Streichresultat-Kennzeichnung |
| **Endlauf-Qualifikation** | aktueller Stand der Top-10-Plätze |
| Sportbeschreibung / Ausschreibung / Downloads | bestehende Inhalte, sauber übernommen |
**Intern (Login):**
| Seite | Inhalt |
|---|---|
| Nennungsannahme | Fahrer suchen/anlegen, Klasse automatisch aus Jahrgang vorschlagen, Startnummer vergeben, Nenngeld quittieren |
| Ergebniserfassung | schnelle Eingabe Zeit/Fehler je Startnummer, tastaturoptimiert |
| Auswertung | Wertungsklassen bilden, rechnen, vorläufig/final setzen |
| Korrekturen | mit Begründung, mit Historie |
| Exporte | Ergebnisliste (PDF, ADAC-Layout), Schlussbericht, Nennliste, CSV |
| **Messungen zuordnen** | unzugeordnete/unplausible Lichtschranken-Messungen einem Start zuweisen oder mit Grund verwerfen |
| **Geräte** | Mess-Pi-Status (Heartbeat, Queue-Länge), Token vergeben/sperren, Entprellzeit und Messpunkte konfigurieren |
| Import-Monitor | Import-Jobs, Warnungen, Fahrer-Dubletten mergen |
| Stammdaten | Saison, Klassen, Vereine, Punktetabelle, Benutzer |
**Anzeige am Parcours** (eigener, minimaler Modus für den Mess-Pi):
| Seite | Inhalt |
|---|---|
| `/anzeige` | aktueller Starter groß (Nr., Name, Klasse, Fahrzeug), laufende/letzte Zeit, nächste Starter; SSE-Update; Kiosk-tauglich; **behält bei Verbindungsverlust die letzte Startliste** und zeigt den Offline-Zustand deutlich an |
**Anforderungen quer:** responsiv (am Platz wird das Handy benutzt) · Deep-Links auf jede
Ergebnisliste · druckbare Ansichten · Aushang-/Beamer-Modus (große Schrift,
Autorefresh) · barrierearm (Kontrast, Tabellen-Semantik)
---
## 8. Arbeitspakete
### AP 0 — Klärung & Setup *(vor allem anderen)*
- Offene Fragen §10 mit dem Verein / Bereichsleiter klären
- **ADAC-Turnierordnung 2026 (`MT-D2025.pdf`) beschaffen und Wertungsregeln verifizieren**
- Punktetabelle Meisterschaft beschaffen
- `git` installieren, Repository anlegen, Python-venv, Projektgerüst
- Zugriff auf die Excel-Vorlagen und ein paar echte Nennlisten
### AP 1 — Datenmodell & Migrationen
- SQLAlchemy-Modelle + Alembic-Initialmigration
- Stammdaten-Seed: Saison 2026, Klassen J 1J 5, S/A-Klassen, Vereine
- Unit-Tests auf Constraints (Mehrfachstart erlaubt, Startnummer eindeutig je Turnier)
### AP 2 — Wertungs-Engine *(der Kern — zuerst und mit Tests)*
- Regelstrategien je Kategorie (Jugend: Fehler → Zeit; Erwachsene: Zeit + Fehler)
- Platzierung inkl. Gleichstandsauflösung, DNF/DNS/DSQ-Behandlung
- **Regressionstest gegen die reale Ergebnisliste vom 31.05.2026** — alle 13 Wertungs-
blöcke müssen exakt die publizierten Platzierungen reproduzieren. Das ist der
Abnahmetest für die Engine.
### AP 3 — Meisterschaftsberechnung
- Punktevergabe, Streichresultate nach Tabelle §3.4
- Tie-Break „Majorität der Siege"
- Endlauf-Qualifikationsvorschlag Top 10 / Top 10 Jugend
- Nachrechnen gegen einen veröffentlichten HTH-Jahresstand als Test
### AP 4 — API
- Read-Endpunkte + OpenAPI-Doku
- Ingest-Endpunkte, Idempotenz, `dry_run`, API-Key-Auth, Rate-Limit
- Import-Job-Protokollierung
- Fahrer-Matching inkl. Merge
### AP 5 — Zeitmessung: Geräte-API & Mess-Pi
- Geräte-Registrierung, Token, Heartbeat, zentrale Konfiguration
- `POST /zeitmessungen` mit Batch, Idempotenz, Einzelquittung, Plausibilitätsprüfung
- Auflösung `startnummer_gemeldet` → Start; unzugeordnete Messungen im Frontend behandeln
- **Client auf dem Mess-Pi:** GPIO-Trigger, Entprellung, monotone Zeitmessung, lokale
SQLite-Queue, Retry mit Backoff, Kiosk-Anzeige mit Offline-Rückfall
- SSE-Endpunkt und `/anzeige`-Seite
- **Abnahmetest:** Netzstecker während der Messung ziehen — nach Wiederverbindung müssen
alle Messungen genau einmal in der DB stehen. Ebenso: Batch doppelt senden → keine Dubletten.
- **Kalibrierung:** gemessene Zeiten gegen eine Handstoppung/Referenz vergleichen, bevor
produktiv damit gewertet wird
### AP 6 — Öffentliches Frontend
- Layout, Navigation, responsive Tabellen
- Termine, Ergebnisse, Fahrerliste, Fahrerprofil, Meisterschaft
- iCal, CSV, Druckansichten
### AP 7 — Internes Frontend
- Login/Rollen, Nennungsannahme, Ergebniserfassung, Auswertung, Korrekturen
- Exporte (PDF-Ergebnisliste im ADAC-Layout, Schlussbericht)
### AP 8 — Datenübernahme Altbestand
- Import der PDF-Ergebnisse ab einem zu definierenden Jahr
(⚠️ die PDFs nutzen Subset-Fonts mit eigener Kodierung — reine Textextraktion schlägt
bei den Meisterschafts-PDFs fehl; realistisch ist ein **halbautomatischer** Import mit
manueller Nacharbeit, ggf. direkt aus den Excel-Originalen statt aus den PDFs)
- Vereinheitlichung Vereinsnamen, Dublettenbereinigung Fahrer
### AP 9 — Deployment & Betrieb
- systemd-Unit, nginx-vHost, TLS
- Backup-Automatik + **getesteter Restore**
- Monitoring, Logrotation
- Einweisung der Auswerter, Kurzanleitung
### AP 10 — Pilotbetrieb
- Eine Veranstaltung **parallel** zu Excel auswerten und Ergebnisse vergleichen
- **Zeitmessung zunächst nur mitlaufen lassen**, nicht werten: Lichtschranken-Zeit gegen
die manuell gestoppte Zeit stellen. Erst wenn beide über eine ganze Veranstaltung
übereinstimmen, wird die Messung wertungsrelevant.
- Erst nach fehlerfreiem Parallellauf umstellen
---
## 9. Ausbaustufen nach dem Kern
- **Online-Voranmeldung** — die Rahmenausschreibung wünscht ausdrücklich Voranmeldung
größerer Teilnehmergruppen und eine Anmeldeliste; das ist die naheliegendste Erweiterung
- **Aufgaben-Einzelwertung** (Tabellen aus §4 bereits vorgesehen) → „woran verliere ich Zeit?"
- Bildarchiv mit Verknüpfung Foto ↔ Fahrer ↔ Veranstaltung
- Automatische Meldung von Schlussbericht/Ergebnisliste an den ADAC
- Mehrmandantenfähigkeit für andere Ortsclubs (MSC Schotten, MSC Hainstadt, PMS Kassel …)
- Übernahme der übrigen oamc.de-Rubriken aus dem Frameset
---
## 10. Offene Fragen
| # | Frage | Warum blockierend |
|---|---|---|
| **O-1** | **Punktetabelle der Meisterschaft** (Platz → Punkte) | AP 3 nicht baubar |
| **O-2** | Zuordnung `S 1``S 9` und `A 1``A 4` → Fahrzeugtyp/Hubraum/kW | Stammdaten-Seed |
| **O-3** | Zählen **A-Fahrer** zur Meisterschaft? Die Rahmenausschreibung sagt „Gesamtklassement der A- und S-Fahrer/innen", die Spalte `Kategorie` ist bei A-Fahrern in der Ergebnisliste jedoch **leer** (bei S steht „S", bei Jugend „J") | AP 3 |
| **O-4** | Gilt die Jugend-Wertung (nur Fehler) auch beim Bundesendlauf, oder dort Zeit+Fehler? | AP 2 |
| **O-5** | Behandlung von DNF / DNS / Disqualifikation in Tages- und Jahreswertung | AP 2/3 |
| ~~O-6~~ | ~~Wer soll die API bespielen?~~**beantwortet:** ein zweiter Raspberry Pi an der Lichtschranke, der Zeiten sendet und Starter-/Fahrzeugdaten anzeigt (§3.6) | erledigt |
| **O-7** | **Ist am ADAC-Platz ein verlässliches Netz?** Wenn nein: muss der Anwendungs-Pi mit vor Ort? WLAN, LTE oder Kabel zwischen Mess-Pi und Server? | **hoch** — bestimmt die gesamte Betriebsarchitektur (§5.3) |
| **O-8** | Ab welchem Jahr sollen Altdaten übernommen werden (PDFs reichen bis 2002 zurück)? | Aufwand AP 8 |
| **O-9** | Wo wird gehostet — dieser Raspberry Pi, ein Server beim Verein, Webhosting? | AP 9, Erreichbarkeit |
| **O-10** | Verhältnis zur bestehenden oamc.de: Unterseite, Subdomain (z. B. `mt.oamc.de`) oder Ablösung? | Navigation, DNS |
| **O-11** | Wer bekommt Schreibrechte, wie viele Auswerter, gemeinsamer Account oder je Person? | AP 7 |
| **O-12** | Liegen die Excel-Originale der Altveranstaltungen noch vor? | macht AP 8 um ein Vielfaches einfacher |
### Neue Fragen zur Zeitmessung
| # | Frage | Warum blockierend |
|---|---|---|
| **O-13** | Was misst die Lichtschranke genau? **Eine** Gesamtdurchfahrt, **mehrere Durchgänge** oder einzelne Abschnitte (Langsamfahrstrecke …)? Woraus setzt sich `Summe Zeit` zusammen? | bestimmt `zeitmessung.messpunkt` und die Summenbildung; AP 5 |
| **O-14** | **Woher weiß der Mess-Pi, wer gerade fährt?** Startnummer manuell eintippen, Warteschlange „nächster Starter" aus der Nennliste, Transponder/RFID? | kritischster Punkt der Zuordnung; AP 5 |
| **O-15** | Was genau soll die **Anzeige** zeigen und auf welcher Hardware (HDMI-Monitor, kleines Display, LED-Tafel)? Für wen — Fahrer, Publikum, Wertungspersonal? | Gestaltung `/anzeige`; AP 5 |
| **O-16** | Geforderte **Messgenauigkeit**? Die Listen führen 1/100 s. Reicht Userspace-GPIO oder braucht es dedizierte Hardware? Ein oder zwei Lichtschranken (Start **und** Ziel)? | Hardware-Auswahl, AP 5 |
| **O-17** | **Wie kommen die Fehlerpunkte in die Anwendung** — Zettel vom Wertungspersonal und zentrale Eingabe, oder Tablets im Parcours? | AP 7; Phase 2 könnte hier ansetzen |
| **O-18** | Löst die Lichtschranke **automatisch** aus (Fahrzeug unterbricht Strahl) oder gibt es zusätzlich einen manuellen Start-/Stopp-Taster als Rückfall? | Ausfallkonzept, AP 5 |
| **O-19** | Wer baut/betreut die **Hardware** (Lichtschranke, Verkabelung, Stromversorgung am Platz)? Existiert sie schon oder wird sie beschafft? | Terminplanung |
---
## 11. Datenschutz
**Das ist hier kein Formalpunkt.** Es werden **Namen, Geburtsjahre, Geschlecht und
Vereinszugehörigkeit von Kindern ab 7 Jahren** verarbeitet und **öffentlich im Internet
angezeigt**. Die heutige Praxis (Namen in öffentlichen PDFs) wird durch eine
durchsuchbare Datenbank mit Personenprofilen **qualitativ verändert** — aus einer
schwer auffindbaren PDF-Liste wird ein über Jahre verknüpftes Profil eines Minderjährigen.
Das ist datenschutzrechtlich eine andere Kategorie und muss vor dem Livegang geklärt sein.
Zu klären und umzusetzen:
- **Rechtsgrundlage** je Verarbeitung; Einwilligung der Erziehungsberechtigten für die
Veröffentlichung von Jugenddaten (idealerweise in Nennformular/Haftungsausschluss
aufnehmen — die Formulare werden ohnehin unterschrieben)
- **Datenminimierung öffentlich:** Geburts*jahr* statt Geburtsdatum, kein Wohnort,
keine Kontaktdaten, keine ADAC-Mitgliedsnummer
- **`robots.txt` / `noindex`** für Fahrerprofile — Ergebnislisten dürfen auffindbar sein,
Personenprofile von Minderjährigen sollten es nicht sein
- **Opt-out**: Anzeige als „Teilnehmer 12" oder Namenskürzel auf Wunsch
- **Löschkonzept / Aufbewahrungsfristen**, Auskunfts- und Berichtigungsrecht
- **Verzeichnis von Verarbeitungstätigkeiten**, TOMs (Backup-Verschlüsselung, TLS,
Zugriffsrollen, Protokollierung)
- Ggf. **Auftragsverarbeitung** mit dem Hoster
> Empfehlung: Vor AP 6 (öffentliches Frontend) eine Entscheidung des Vorstands
> herbeiführen, wie Jugenddaten öffentlich dargestellt werden. Die technische Umsetzung
> ist einfach — die Entscheidung ist es nicht, und sie nachträglich zu ändern bedeutet,
> bereits indexierte Seiten wieder einzufangen.
---
## 12. Risiken
| Risiko | Auswirkung | Gegenmaßnahme |
|---|---|---|
| Wertungsregeln in §3.3 sind aus PDFs **abgeleitet**, nicht bestätigt | falsche Platzierungen, Vertrauensverlust | AP 0: Turnierordnung beschaffen; AP 2: Regressionstest gegen echte Listen; AP 10: Parallellauf |
| Ausfall am Turniertag (Pi, Netz, Strom) | Veranstaltung blockiert | Excel-Fallback bereithalten, bis der Parallellauf sauber ist; Export vor Ort möglich halten |
| **Lichtschranke fällt aus oder misst falsch** | Zeiten des laufenden Turniers fehlen — **nicht nachholbar**, die Fahrt ist vorbei | manuelle Zeitnahme als Rückfall **immer** parallel bereithalten; manuelle Nacherfassung im Frontend; Plausibilitätsprüfung serverseitig; AP 10 misst zunächst nur mit |
| **Fehlauslösung** durch Zuschauer/Helfer/rangierendes Motorrad | falsche Zeit im Ergebnis | Entprellung auf dem Pi, Verwerfen-Funktion mit Grund, Plausibilitätsgrenzen, Vier-Augen-Prinzip vor „final" |
| **Messung dem falschen Fahrer zugeordnet** | zwei Ergebnisse gleichzeitig falsch, schwer zu bemerken | Zuordnung (O-14) früh und sauber klären; Anzeige am Parcours macht die Zuordnung für alle sichtbar → Fehler fällt sofort auf |
| Netzausfall zwischen Mess-Pi und Server | Messungen gehen verloren | lokale Queue + Idempotenz (§3.6c); Abnahmetest in AP 5 zieht bewusst den Netzstecker |
| Altdaten-Import aus PDFs scheitert an Font-Kodierung | AP 8 wird teuer | Excel-Originale beschaffen (O-12); Import zeitlich begrenzen; notfalls Start ab 2026 |
| Fahrer-Dubletten durch Namensvarianten | falsche Meisterschaftsstände | Matching + Merge-Werkzeug in AP 4, Import-Report mit „NEU"-Markierung |
| Datenschutzthema Jugenddaten wird spät geklärt | Livegang verzögert oder Rückbau | §11 vor AP 6 entscheiden |
| Projekt hängt an einer Person | keine Wartung | Doku, sauberes Repo, zweite eingewiesene Person |
| Raspberry Pi: SD-Karte/Speicherausfall | Datenverlust | Backup extern + Restore testen, SSD statt SD-Karte erwägen; auf dem Mess-Pi read-only Root-FS erwägen |
| Scope-Ausweitung (Online-Nennung, Bildarchiv …) | Kern wird nie fertig | §9 ist bewusst *nach* dem Kern; AP 10 ist der Zielpunkt |
---
## 13. Nächster Schritt
1. Diesen Plan im Verein durchgehen, insbesondere **§10 (offene Fragen)** und
**§11 (Datenschutz)**
2. **ADAC-Turnierordnung 2026 und Punktetabelle beschaffen** — ohne die stehen AP 2 und AP 3
3. **Zeitmessung klären: O-13 (was wird gemessen), O-14 (Zuordnung zum Fahrer),
O-16 (Genauigkeit/Hardware), O-19 (wer baut sie)** — davon hängt AP 5 komplett ab
4. Entscheidung zu **O-7** (Netz am Platz) und **O-9/O-10** (Hosting, Domain)
5. Danach AP 0 → AP 1 → AP 2
> **Reihenfolge-Hinweis:** Die Zeitmessung ist das sichtbarste, aber nicht das erste
> Arbeitspaket. Sie liefert nur die Hälfte eines Ergebnisses (§3.6) und braucht Turnier,
> Startliste und Wertungslogik als Unterbau. AP 13 zuerst, dann AP 5 — sonst hat der
> Mess-Pi nichts, wogegen er Startnummern auflösen könnte.
---
### Quellen
- `www.oamc.de` — Startseite, Navigation, Rubrik Motorradgruppe (`m/m1.htm`, `m/m2.htm`,
`m/m-sportbeschreibung.htm`), Downloadseite `d/d-mot-turnier.htm`
- `www.oamc.de/datei/2026-05-31-MT-REIN-HAIN.PDF` — Tagesergebnis Doppelturnier
MSC Hainstadt / OAMC Reinheim, 13 Wertungsblöcke; Grundlage für §3.3
- `www.oamc.de/datei/MT-Rahmenausschreibung-2026.pdf` (Stand 1.2.2026) — Nenngeld,
Nennschluss, Streichresultate, Jugend-Klasseneinteilung, Endlaufqualifikation
- Systemprüfung auf dem Zielgerät (Raspberry Pi 4B) für §5.1