"""Start (eine Teilnahme) und Ergebnis (1:1 zum Start). Schluessel eines Ergebnisses ist (Turnier, Start), NICHT (Turnier, Fahrer) — ein Fahrer kann am selben Tag mehrfach starten (Plan §3.3 Mehrfachstarts). """ from __future__ import annotations from datetime import datetime from decimal import Decimal from sqlalchemy import ( Boolean, DateTime, ForeignKey, Integer, Numeric, String, Text, UniqueConstraint, func, ) from sqlalchemy.orm import Mapped, mapped_column, relationship from oamc.models.base import Base, sa_enum from oamc.models.enums import StartStatus class Start(Base): __tablename__ = "start" __table_args__ = ( UniqueConstraint("turnier_id", "startnummer", name="start_turnier_startnummer"), ) id: Mapped[int] = mapped_column(Integer, primary_key=True) turnier_id: Mapped[int] = mapped_column( ForeignKey("turnier.id", ondelete="CASCADE"), nullable=False, index=True ) fahrer_id: Mapped[int] = mapped_column(ForeignKey("fahrer.id"), nullable=False, index=True) klasse_id: Mapped[int] = mapped_column(ForeignKey("klasse.id"), nullable=False) wertungsklasse_id: Mapped[int | None] = mapped_column( ForeignKey("wertungsklasse.id", ondelete="SET NULL"), index=True ) startnummer: Mapped[int] = mapped_column(Integer, nullable=False) fahrzeug: Mapped[str | None] = mapped_column(String(200)) nenngeld_bezahlt: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False) status: Mapped[StartStatus] = mapped_column( sa_enum(StartStatus, "start_status"), nullable=False, default=StartStatus.GENANNT ) nennreihenfolge: Mapped[int | None] = mapped_column(Integer) # nummerierte Nennungsliste turnier: Mapped[Turnier] = relationship(back_populates="starts") # noqa: F821 fahrer: Mapped[Fahrer] = relationship(back_populates="starts") # noqa: F821 klasse: Mapped[Klasse] = relationship() # noqa: F821 wertungsklasse: Mapped[Wertungsklasse | None] = relationship( # noqa: F821 back_populates="starts" ) ergebnis: Mapped[Ergebnis | None] = relationship( back_populates="start", cascade="all, delete-orphan", uselist=False ) zeitmessungen: Mapped[list[Zeitmessung]] = relationship( # noqa: F821 back_populates="start" ) @property def gewertet(self) -> bool: return self.status in (StartStatus.GESTARTET,) class Ergebnis(Base): """1:1 zum Start — getrennte Tabelle fuer saubere Nachtraege/Korrekturen. Zwei Quellen (Plan §3.6): ``summe_zeit`` aus ``zeitmessung`` summiert, ``summe_fehler`` manuell erfasst. Ein "halbes" Ergebnis ist der Normalzustand waehrend der Veranstaltung. Gewertet wird erst, wenn beide ``*_vollstaendig``. """ __tablename__ = "ergebnis" start_id: Mapped[int] = mapped_column( ForeignKey("start.id", ondelete="CASCADE"), primary_key=True ) summe_zeit: Mapped[Decimal | None] = mapped_column(Numeric(7, 2)) # aus zeitmessung summiert summe_fehler: Mapped[int | None] = mapped_column(Integer) # manuell erfasst gesamt: Mapped[Decimal | None] = mapped_column(Numeric(9, 2)) # berechnet platzierung: Mapped[int | None] = mapped_column(Integer) # berechnet zeit_vollstaendig: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False) fehler_vollstaendig: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False) erfasst_von: Mapped[str | None] = mapped_column(String(120)) erfasst_am: Mapped[datetime] = mapped_column( DateTime(timezone=True), server_default=func.now(), nullable=False ) korrigiert_am: Mapped[datetime | None] = mapped_column(DateTime(timezone=True)) korrektur_grund: Mapped[str | None] = mapped_column(Text) start: Mapped[Start] = relationship(back_populates="ergebnis") @property def wertbar(self) -> bool: return bool(self.zeit_vollstaendig and self.fehler_vollstaendig)