* 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>
55 lines
2.3 KiB
Python
55 lines
2.3 KiB
Python
"""Fahrer-Stammdaten — einmalig, nicht pro Turnier (Plan §4).
|
|
|
|
Loest die heutige \"BAUSTELLE\" ``m/m-fahrer.htm`` ab. Dublettenerkennung beim
|
|
Import: siehe ``oamc.matching.fahrer``.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from datetime import date
|
|
|
|
from sqlalchemy import Boolean, Date, ForeignKey, Integer, String, Text
|
|
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
|
|
|
from oamc.models.base import Base, TimestampMixin, sa_enum
|
|
from oamc.models.enums import Geschlecht
|
|
|
|
|
|
class Fahrer(Base, TimestampMixin):
|
|
__tablename__ = "fahrer"
|
|
# Hinweis: der GIN-Trigramm-Index ``ix_fahrer_name_trgm`` (unscharfe Suche beim
|
|
# Import, Plan §6.4) wird als Ausdrucksindex in der Migration angelegt und in
|
|
# ``migrations/env.py`` von der Autogenerate-Vergleichung ausgenommen.
|
|
|
|
id: Mapped[int] = mapped_column(Integer, primary_key=True)
|
|
nachname: Mapped[str] = mapped_column(String(120), nullable=False)
|
|
vorname: Mapped[str] = mapped_column(String(120), nullable=False)
|
|
geburtsjahr: Mapped[int | None] = mapped_column(Integer, index=True)
|
|
# nullable: nur fuer die J-1-Pruefung "am Veranstaltungstag 7 Jahre" noetig.
|
|
# Datenminimierung oeffentlich: nur das Jahr wird angezeigt (Plan §11).
|
|
geburtsdatum: Mapped[date | None] = mapped_column(Date)
|
|
geschlecht: Mapped[Geschlecht | None] = mapped_column(sa_enum(Geschlecht, "geschlecht"))
|
|
verein_id: Mapped[int | None] = mapped_column(ForeignKey("verein.id"), index=True)
|
|
|
|
adac_mitgliedsnr: Mapped[str | None] = mapped_column(String(40)) # nie oeffentlich
|
|
jugendausweis_gueltig_bis: Mapped[date | None] = mapped_column(Date)
|
|
|
|
# Datenschutz: Opt-out auf Anzeige als "Teilnehmer N" / Kuerzel (Plan §11)
|
|
anzeige_anonymisiert: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False)
|
|
|
|
aktiv: Mapped[bool] = mapped_column(Boolean, nullable=False, default=True)
|
|
notiz: Mapped[str | None] = mapped_column(Text)
|
|
|
|
verein: Mapped[Verein | None] = relationship(back_populates="fahrer") # noqa: F821
|
|
starts: Mapped[list[Start]] = relationship(back_populates="fahrer") # noqa: F821
|
|
|
|
@property
|
|
def voller_name(self) -> str:
|
|
return f"{self.nachname} {self.vorname}"
|
|
|
|
@property
|
|
def anzeigename(self) -> str:
|
|
if self.anzeige_anonymisiert:
|
|
return f"Teilnehmer/in #{self.id}"
|
|
return self.voller_name
|