* 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>
117 lines
4.4 KiB
Python
117 lines
4.4 KiB
Python
"""Abnahmetest der Wertungs-Engine gegen die reale Ergebnisliste
|
|
``2026-05-31-MT-REIN-HAIN.PDF`` (Plan §3.3, AP 2).
|
|
|
|
Die vollstaendigen 13 Wertungsbloecke liegen als PDF vor; hier abgebildet sind
|
|
die im Plan zitierten Belegzeilen. Wo die zitierten Zeilen einen zusammenhaengenden
|
|
Platzbereich bilden, wird die exakte publizierte Platzierung reproduziert; wo
|
|
Zeilen aus der Mitte des Feldes fehlen, wird die relative Ordnung geprueft.
|
|
|
|
TODO(AP 2): sobald der volle PDF-Text vorliegt, alle 13 Bloecke vollstaendig
|
|
hinterlegen und 1:1 gegen die publizierten Plaetze pruefen.
|
|
"""
|
|
|
|
from decimal import Decimal
|
|
|
|
import pytest
|
|
|
|
from oamc.models.enums import Kategorie, StartStatus
|
|
from oamc.wertung.engine import WertungsEingabe, platziere
|
|
|
|
|
|
def _e(sid, kat, zeit, fehler):
|
|
return WertungsEingabe(
|
|
start_id=sid,
|
|
kategorie=kat,
|
|
status=StartStatus.GESTARTET,
|
|
summe_zeit=Decimal(str(zeit)),
|
|
summe_fehler=fehler,
|
|
)
|
|
|
|
|
|
# --- Block "S 5, S 9, S 3, S 6, S 7" (Erwachsene) -----------------------
|
|
S_BLOCK = [
|
|
# (name, klasse, zeit, fehler, publizierter_platz)
|
|
("Bernius Meik", "S 6", "105.43", 0, 1),
|
|
("Schmunk Christian", "S 9", "103.26", 10, 2),
|
|
("Krack Robin", "S 5", "130.33", 0, 4),
|
|
("Steinbrech Nicolai", "S 5", "65.71", 1210, 11),
|
|
]
|
|
|
|
|
|
def test_s_block_gesamt_exakt():
|
|
erwartet = {
|
|
"Bernius Meik": Decimal("105.43"),
|
|
"Schmunk Christian": Decimal("113.26"),
|
|
"Krack Robin": Decimal("130.33"),
|
|
"Steinbrech Nicolai": Decimal("1275.71"),
|
|
}
|
|
eingaben = [_e(i, Kategorie.STAMM, z, f) for i, (_n, _k, z, f, _p) in enumerate(S_BLOCK)]
|
|
res = {i: r for i, r in enumerate(platziere(eingaben))}
|
|
for i, (name, _k, _z, _f, _p) in enumerate(S_BLOCK):
|
|
assert res[i].gesamt == erwartet[name], name
|
|
|
|
|
|
def test_s_block_relative_ordnung_entspricht_publikation():
|
|
eingaben = [_e(i, Kategorie.STAMM, z, f) for i, (_n, _k, z, f, _p) in enumerate(S_BLOCK)]
|
|
res = {i: r for i, r in enumerate(platziere(eingaben))}
|
|
# publizierte Plaetze aufsteigend -> unsere internen Plaetze muessen dieselbe
|
|
# Reihenfolge haben
|
|
intern = [res[i].platzierung for i in range(len(S_BLOCK))]
|
|
publiziert = [p for *_r, p in S_BLOCK]
|
|
assert [x for _, x in sorted(zip(publiziert, intern))] == sorted(intern)
|
|
assert intern == sorted(intern) # bereits in publizierter Reihenfolge gelistet
|
|
|
|
|
|
# --- Block "J 3" (Jugend) --------------------------------------------
|
|
# Belegzeilen aus dem Plan. Plaetze 5+6 fehlen im Zitat -> mit klar markierten
|
|
# Fuellzeilen (Fehler zwischen 10 und 60) besetzt, damit die publizierten Plaetze
|
|
# 1,2,3,4,7,8 exakt herauskommen.
|
|
J3_BELEGE = [
|
|
("Freund Philipp", "136.88", 0, 1),
|
|
("D'Angelo Leandro", "140.40", 0, 2),
|
|
("Heil Luca", "144.46", 0, 3),
|
|
("Mattes Flynn", "160.05", 10, 4),
|
|
("Ratter Mitja", "148.42", 60, 7),
|
|
("Kemesies Finlay", "165.39", 60, 8),
|
|
]
|
|
J3_FUELLER = [ # SYNTHETIC_FILLER — nicht aus der Quelle, nur Platz 5/6
|
|
("SYNTHETIC_FILLER_P5", "150.00", 20, 5),
|
|
("SYNTHETIC_FILLER_P6", "150.00", 50, 6),
|
|
]
|
|
|
|
|
|
def test_j3_block_reproduziert_publizierte_plaetze():
|
|
alle = J3_BELEGE + J3_FUELLER
|
|
eingaben = [_e(i, Kategorie.JUGEND, z, f) for i, (_n, z, f, _p) in enumerate(alle)]
|
|
res = {i: r for i, r in enumerate(platziere(eingaben))}
|
|
for i, (name, _z, _f, publiziert) in enumerate(alle):
|
|
assert res[i].platzierung == publiziert, name
|
|
# Gesamt der Jugend = Fehler
|
|
assert res[i].gesamt == Decimal(alle[i][2]).quantize(Decimal("0.01")), name
|
|
|
|
|
|
def test_j3_drei_nuller_werden_ueber_die_zeit_sortiert():
|
|
eingaben = [_e(i, Kategorie.JUGEND, z, f) for i, (_n, z, f, _p) in enumerate(J3_BELEGE[:3])]
|
|
res = platziere(eingaben)
|
|
assert [r.platzierung for r in res] == [1, 2, 3] # 136,88 < 140,40 < 144,46
|
|
|
|
|
|
def test_j3_zwei_mit_60_fehlern_zeitentscheid():
|
|
# Ratter 148,42 vor Kemesies 165,39
|
|
eingaben = [
|
|
_e(0, Kategorie.JUGEND, "148.42", 60),
|
|
_e(1, Kategorie.JUGEND, "165.39", 60),
|
|
]
|
|
res = platziere(eingaben)
|
|
assert res[0].platzierung == 1 and res[1].platzierung == 2
|
|
|
|
|
|
@pytest.mark.parametrize("name,zeit,fehler,gesamt", [
|
|
("Freund Philipp", "136.88", 0, "0.00"),
|
|
("Mattes Flynn", "160.05", 10, "10.00"),
|
|
("Ratter Mitja", "148.42", 60, "60.00"),
|
|
])
|
|
def test_j3_einzelne_gesamtwerte(name, zeit, fehler, gesamt):
|
|
res = platziere([_e(0, Kategorie.JUGEND, zeit, fehler)])
|
|
assert res[0].gesamt == Decimal(gesamt)
|