* 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>
96 lines
3.0 KiB
Python
96 lines
3.0 KiB
Python
"""Einheitstests der Wertungsformeln (Plan §3.3)."""
|
|
|
|
from decimal import Decimal
|
|
|
|
from oamc.models.enums import Kategorie, StartStatus
|
|
from oamc.wertung.engine import WertungsEingabe, platziere, regel_fuer
|
|
|
|
|
|
def _e(sid, kat, zeit=None, fehler=None, status=StartStatus.GESTARTET, **kw):
|
|
return WertungsEingabe(
|
|
start_id=sid,
|
|
kategorie=kat,
|
|
status=status,
|
|
summe_zeit=Decimal(str(zeit)) if zeit is not None else None,
|
|
summe_fehler=fehler,
|
|
**kw,
|
|
)
|
|
|
|
|
|
def test_erwachsene_gesamt_ist_zeit_plus_fehler():
|
|
regel = regel_fuer(Kategorie.STAMM)
|
|
assert regel.gesamt(Decimal("105.43"), 0) == Decimal("105.43")
|
|
assert regel.gesamt(Decimal("103.26"), 10) == Decimal("113.26")
|
|
assert regel.gesamt(Decimal("65.71"), 1210) == Decimal("1275.71")
|
|
|
|
|
|
def test_jugend_gesamt_ist_nur_fehler():
|
|
regel = regel_fuer(Kategorie.JUGEND)
|
|
assert regel.gesamt(Decimal("136.88"), 0) == Decimal("0.00")
|
|
assert regel.gesamt(Decimal("148.42"), 60) == Decimal("60.00")
|
|
|
|
|
|
def test_jugend_gleichstand_wird_ueber_zeit_aufgeloest():
|
|
res = platziere(
|
|
[
|
|
_e(1, Kategorie.JUGEND, 144.46, 0),
|
|
_e(2, Kategorie.JUGEND, 136.88, 0),
|
|
_e(3, Kategorie.JUGEND, 140.40, 0),
|
|
]
|
|
)
|
|
platz = {r.start_id: r.platzierung for r in res}
|
|
assert platz == {2: 1, 3: 2, 1: 3} # schnellste Zeit vorn
|
|
|
|
|
|
def test_erwachsene_platzierung_aufsteigend_nach_gesamt():
|
|
res = platziere(
|
|
[
|
|
_e(1, Kategorie.STAMM, 130.33, 0), # 130.33
|
|
_e(2, Kategorie.STAMM, 105.43, 0), # 105.43
|
|
_e(3, Kategorie.STAMM, 103.26, 10), # 113.26
|
|
]
|
|
)
|
|
platz = {r.start_id: r.platzierung for r in res}
|
|
assert platz == {2: 1, 3: 2, 1: 3}
|
|
|
|
|
|
def test_dnf_dns_dsq_werden_nicht_klassiert():
|
|
res = platziere(
|
|
[
|
|
_e(1, Kategorie.STAMM, 100.0, 0),
|
|
_e(2, Kategorie.STAMM, 90.0, 0, status=StartStatus.DNF),
|
|
_e(3, Kategorie.STAMM, None, None, status=StartStatus.DNS),
|
|
]
|
|
)
|
|
by_id = {r.start_id: r for r in res}
|
|
assert by_id[1].platzierung == 1
|
|
assert by_id[2].platzierung is None and not by_id[2].gewertet
|
|
assert by_id[3].platzierung is None
|
|
|
|
|
|
def test_unvollstaendiges_ergebnis_bleibt_vorlaeufig_ohne_platz():
|
|
res = platziere(
|
|
[
|
|
_e(1, Kategorie.STAMM, 100.0, 0, fehler_vollstaendig=False),
|
|
_e(2, Kategorie.STAMM, 110.0, 0),
|
|
]
|
|
)
|
|
by_id = {r.start_id: r for r in res}
|
|
assert by_id[1].platzierung is None
|
|
assert by_id[1].gesamt == Decimal("100.00") # vorlaeufige Gesamtanzeige
|
|
assert "vorlaeufig" in by_id[1].hinweis
|
|
assert by_id[2].platzierung == 1
|
|
|
|
|
|
def test_echter_gleichstand_teilt_platz_und_laesst_luecke():
|
|
# identische Zeit UND Fehler -> selber Platz, danach Luecke
|
|
res = platziere(
|
|
[
|
|
_e(1, Kategorie.STAMM, 100.00, 0),
|
|
_e(2, Kategorie.STAMM, 100.00, 0),
|
|
_e(3, Kategorie.STAMM, 105.00, 0),
|
|
]
|
|
)
|
|
platz = sorted(r.platzierung for r in res)
|
|
assert platz == [1, 1, 3]
|