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>
This commit is contained in:
linus
2026-09-05 18:49:00 +02:00
co-authored by Claude Sonnet 5
commit 03f6fdb0d9
115 changed files with 8041 additions and 0 deletions
+138
View File
@@ -0,0 +1,138 @@
# OAMC Turnierauswertung
Webanwendung zur Auswertung von **ADAC Motorrad-Turnieren** des OAMC Reinheim e.V.:
Ergebnisse (Fehlerpunkte + Zeit → Platzierung), Fahrerdatenbank mit Historie,
Meisterschaftswertung und ein **Endpoint für die Lichtschranken-Zeitmessung**.
Fachliche Grundlagen, offene Fragen und Architekturbegründung: **[`plan.md`](plan.md)**.
> Der Mess-Pi-Client (GPIO/Lichtschranke) ist **nicht** Teil dieses Repos — die
> Anwendung stellt nur den Endpoint bereit. Contract:
> [`docs/zeitmessung-endpoint.md`](docs/zeitmessung-endpoint.md).
---
## Stack
| Schicht | Wahl |
|---|---|
| DB | PostgreSQL 17 (JSONB, ARRAY, `pg_trgm`) |
| Backend | Python 3.11+ · FastAPI · SQLAlchemy 2 · Alembic · Pydantic v2 |
| Frontend | serverseitig gerendertes Jinja2 + HTMX (kein Node, kein Build) |
| Live | Server-Sent Events (`sse-starlette`) |
| Auslieferung | **Docker-Image** *und* **Debian-Paket** (`.deb` / eigenes APT-Repo) |
## Projektstruktur
```
src/oamc/
models/ ORM (§4 im Plan)
wertung/ Wertungs-Engine — der Kern (§3.3). engine.py + service.py
meisterschaft/ Punkte, Streichresultate, Tie-Break, Endlaufquali (§3.4)
matching/ Fahrer-Dublettenerkennung + Merge (§6.4)
importer/ Bulk-Ingest + Excel-Leser
geraete/ Zeitmessungs-Endpoint-Logik (§3.6)
api/v1/ read.py · ingest.py · geraete.py · admin.py
frontend/ public.py · intern.py + templates/ static/
migrations/ Alembic
tests/ pytest gegen echte PostgreSQL-Testdatenbank
docker/ Dockerfile · nginx.conf · entrypoint.sh
debian/ .deb-Paketierung (venv unter /opt/oamc-turnier)
deploy/ systemd-Unit · nginx-vHost · Backup-Skript (bare metal)
packaging/apt-repo/ reprepro-Konfiguration + Build-Skript
```
## Entwicklung
```sh
python3 -m venv .venv && . .venv/bin/activate
pip install -e ".[dev]"
cp .env.example .env # DATABASE_URL anpassen
createdb oamc_dev # PostgreSQL-Rolle mit CREATEDB nötig
alembic upgrade head
oamc-turnier seed # Stammdaten Saison 2026
oamc-turnier benutzer-anlegen admin --rolle admin
oamc-turnier serve --reload # http://127.0.0.1:8000 · /docs
```
### Tests
Brauchen eine erreichbare PostgreSQL-Testdatenbank (`OAMC_DATABASE_URL`, Default
`…/oamc_test`). Das Schema wird pro Lauf frisch migriert, jeder Test läuft in einer
zurückgerollten Transaktion.
```sh
createdb oamc_test
pytest
```
Der **Abnahmetest der Wertungs-Engine** (`tests/test_wertung_regression_2026_05_31.py`)
rechnet die im Plan §3.3 zitierten Zeilen der realen Ergebnisliste vom 31.05.2026 nach.
## Auslieferung 1 — Docker / „docker repo"
```sh
cp .env.example .env # OAMC_SECRET_KEY setzen!
docker compose up -d # db + app + nginx → http://localhost:8080
docker compose exec app oamc-turnier seed
# Image für eine Registry bauen/pushen:
docker build -t <registry>/oamc-turnier:0.1.0 -f docker/Dockerfile .
docker push <registry>/oamc-turnier:0.1.0
```
Multi-Arch für den Raspberry Pi 4:
```sh
docker buildx build --platform linux/arm64,linux/amd64 \
-t <registry>/oamc-turnier:0.1.0 -f docker/Dockerfile --push .
```
Migrationen laufen automatisch beim Containerstart (`docker/entrypoint.sh`).
## Auslieferung 2 — Debian-Paket / eigenes APT-Repo
```sh
sudo apt install build-essential debhelper devscripts python3-venv
./packaging/apt-repo/build-deb.sh # → ../oamc-turnier_0.1.0_*.deb
sudo apt install ../oamc-turnier_0.1.0_*.deb
```
Das Paket bringt ein eigenes virtualenv unter `/opt/oamc-turnier/venv`, eine
systemd-Unit und Beispielkonfiguration mit. Danach:
```sh
sudoedit /etc/oamc-turnier/oamc-turnier.env # DATABASE_URL, SECRET_KEY
sudo systemctl enable --now oamc-turnier
sudo -u oamc /opt/oamc-turnier/venv/bin/oamc-turnier seed
```
Eigenes signiertes APT-Repo aufsetzen: [`packaging/apt-repo/README.md`](packaging/apt-repo/README.md).
## API-Überblick (`/docs` für die vollständige OpenAPI-Doku)
| Bereich | Auth | Beispiele |
|---|---|---|
| **Read** | | `GET /api/v1/turniere/{id}/ergebnisse`, `/api/v1/fahrer?q=`, `/api/v1/meisterschaften/{id}/stand` |
| **Ingest** | `X-API-Key` | `POST /api/v1/turniere/{id}/ergebnisse` (idempotent, `?dry_run=true`), `POST …/import/xlsx` |
| **Geräte** | `Authorization: Bearer <geräte-token>` | `POST /api/v1/zeitmessungen` (Batch, idempotent), SSE `GET /api/v1/turniere/aktiv/anzeige` |
| **Admin** | interne Session | `POST /api/v1/admin/geraete`, `…/zeitmessungen/{id}/zuordnen`, `…/meisterschaften/{id}/berechnen`, `…/fahrer/merge` |
## Bekannte offene Punkte (aus `plan.md` §10)
Diese sind **im Code klar markiert** und blockieren einen offiziellen Produktivgang:
* **O-1** — echte Meisterschafts-Punktetabelle fehlt. Es läuft eine
`PLATZHALTER_PUNKTETABELLE`; jeder berechnete Stand trägt einen Warnhinweis.
* **O-2** — Zuordnung `S 1``S 9` / `A 1``A 4` → Fahrzeugtyp: Klassen sind
angelegt, `fahrzeugtyp` bleibt `NULL`.
* **Wertungsregeln §3.3** sind aus PDFs *abgeleitet* — vor Produktivgang gegen die
ADAC-Turnierordnung 2026 zu verifizieren. Die Engine ist regelbasiert
konfigurierbar (`oamc.wertung.engine.REGELN`), nicht fest verdrahtet.
* **O-13/O-14/O-16** — Details der Zeitmessung (was wird gemessen, Zuordnung zum
Fahrer, Genauigkeit): siehe `docs/zeitmessung-endpoint.md`.
* **§11 Datenschutz** — Fahrerprofile sind `noindex` + `robots.txt`-gesperrt,
Geburts*jahr* statt -datum, Opt-out-Anonymisierung (`fahrer.anzeige_anonymisiert`)
ist umgesetzt; die Vorstandsentscheidung zur öffentlichen Darstellung von
Jugenddaten steht noch aus.