Initial commit: MVP tidtakingssystem
- Backend: FastAPI, EXIF-parser, EasyOCR, SQLite - Frontend: React admin (startliste, passeringer, gjennomgang, resultater) - Docker: docker-compose med depot/processed/data-volumer Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,125 @@
|
||||
"""
|
||||
Beregn split-tider og totalresultat fra passeringsloggen.
|
||||
"""
|
||||
|
||||
from datetime import datetime
|
||||
from typing import Optional
|
||||
|
||||
import aiosqlite
|
||||
|
||||
|
||||
async def get_results(db: aiosqlite.Connection) -> list[dict]:
|
||||
"""
|
||||
Hent totalresultat for alle utøvere som har passert start og mål.
|
||||
Returnerer sortert liste med split-tider.
|
||||
"""
|
||||
# Hent alle bekreftede passeringer gruppert per utøver
|
||||
async with db.execute("""
|
||||
SELECT p.profile_id, p.bib_number, a.name, a.club,
|
||||
p.station, p.timestamp_utc
|
||||
FROM passages p
|
||||
LEFT JOIN athletes a ON a.profile_id = p.profile_id
|
||||
WHERE p.needs_review = 0 AND p.profile_id IS NOT NULL
|
||||
ORDER BY p.profile_id, p.timestamp_utc
|
||||
""") as cur:
|
||||
rows = await cur.fetchall()
|
||||
|
||||
# Grupper per utøver
|
||||
athletes: dict[str, dict] = {}
|
||||
for row in rows:
|
||||
pid = row["profile_id"]
|
||||
if pid not in athletes:
|
||||
athletes[pid] = {
|
||||
"profile_id": pid,
|
||||
"bib_number": row["bib_number"],
|
||||
"name": row["name"],
|
||||
"club": row["club"],
|
||||
"passages": [],
|
||||
}
|
||||
athletes[pid]["passages"].append({
|
||||
"station": row["station"],
|
||||
"timestamp_utc": row["timestamp_utc"],
|
||||
})
|
||||
|
||||
results = []
|
||||
for pid, data in athletes.items():
|
||||
passages = data["passages"]
|
||||
if not passages:
|
||||
continue
|
||||
|
||||
# Finn start og mål
|
||||
start_p = next((p for p in passages if p["station"] == "start"), None)
|
||||
finish_p = next((p for p in passages if p["station"] == "finish"), None)
|
||||
|
||||
start_time = _parse_ts(start_p["timestamp_utc"]) if start_p else None
|
||||
finish_time = _parse_ts(finish_p["timestamp_utc"]) if finish_p else None
|
||||
|
||||
total_seconds = None
|
||||
if start_time and finish_time:
|
||||
total_seconds = (finish_time - start_time).total_seconds()
|
||||
|
||||
# Split-tider mellom alle stasjoner
|
||||
splits = []
|
||||
prev_ts = start_time
|
||||
prev_station = "start"
|
||||
for p in passages:
|
||||
if p["station"] == "start":
|
||||
continue
|
||||
ts = _parse_ts(p["timestamp_utc"])
|
||||
split_s = (ts - prev_ts).total_seconds() if prev_ts else None
|
||||
splits.append({
|
||||
"from": prev_station,
|
||||
"to": p["station"],
|
||||
"split_seconds": split_s,
|
||||
"split_formatted": _fmt_seconds(split_s),
|
||||
"timestamp_utc": p["timestamp_utc"],
|
||||
})
|
||||
prev_ts = ts
|
||||
prev_station = p["station"]
|
||||
|
||||
results.append({
|
||||
"profile_id": pid,
|
||||
"bib_number": data["bib_number"],
|
||||
"name": data["name"] or f"Ukjent ({data['bib_number']})",
|
||||
"club": data["club"],
|
||||
"start_time": start_p["timestamp_utc"] if start_p else None,
|
||||
"finish_time": finish_p["timestamp_utc"] if finish_p else None,
|
||||
"total_seconds": total_seconds,
|
||||
"total_formatted": _fmt_seconds(total_seconds),
|
||||
"splits": splits,
|
||||
"dnf": finish_time is None and start_time is not None,
|
||||
"dns": start_time is None,
|
||||
})
|
||||
|
||||
# Sorter: fullført (etter tid), DNF, DNS
|
||||
results.sort(key=_sort_key)
|
||||
return results
|
||||
|
||||
|
||||
def _parse_ts(ts_str: str) -> Optional[datetime]:
|
||||
if not ts_str:
|
||||
return None
|
||||
try:
|
||||
return datetime.fromisoformat(ts_str)
|
||||
except ValueError:
|
||||
return None
|
||||
|
||||
|
||||
def _fmt_seconds(seconds: Optional[float]) -> Optional[str]:
|
||||
if seconds is None:
|
||||
return None
|
||||
seconds = int(seconds)
|
||||
h = seconds // 3600
|
||||
m = (seconds % 3600) // 60
|
||||
s = seconds % 60
|
||||
if h:
|
||||
return f"{h}:{m:02d}:{s:02d}"
|
||||
return f"{m}:{s:02d}"
|
||||
|
||||
|
||||
def _sort_key(r: dict):
|
||||
if r["dns"]:
|
||||
return (3, 0)
|
||||
if r["dnf"]:
|
||||
return (2, 0)
|
||||
return (1, r["total_seconds"] or float("inf"))
|
||||
Reference in New Issue
Block a user