""" CRUD for utøverprofiler og startliste. Bruker aiosqlite for async tilgang fra FastAPI. """ import csv import io import uuid from typing import Optional import aiosqlite DB_PATH = "/data/timing.db" async def init_db(db: aiosqlite.Connection) -> None: await db.executescript(""" CREATE TABLE IF NOT EXISTS athletes ( profile_id TEXT PRIMARY KEY, bib_number TEXT UNIQUE, name TEXT, club TEXT, created_at TEXT NOT NULL DEFAULT (datetime('now')) ); CREATE TABLE IF NOT EXISTS passages ( passage_id TEXT PRIMARY KEY, profile_id TEXT REFERENCES athletes(profile_id), bib_number TEXT, station TEXT NOT NULL, timestamp_utc TEXT NOT NULL, gps_lat REAL, gps_lon REAL, gps_alt REAL, confidence REAL, id_method TEXT, source_image TEXT, needs_review INTEGER NOT NULL DEFAULT 0, review_note TEXT, created_at TEXT NOT NULL DEFAULT (datetime('now')) ); CREATE TABLE IF NOT EXISTS passage_images ( image_id TEXT PRIMARY KEY, passage_id TEXT NOT NULL REFERENCES passages(passage_id) ON DELETE CASCADE, image_path TEXT NOT NULL, timestamp_utc TEXT NOT NULL, proximity_score REAL NOT NULL DEFAULT 0, created_at TEXT NOT NULL DEFAULT (datetime('now')) ); CREATE INDEX IF NOT EXISTS idx_passages_profile ON passages(profile_id); CREATE INDEX IF NOT EXISTS idx_passages_station ON passages(station); CREATE INDEX IF NOT EXISTS idx_passages_needs_review ON passages(needs_review); CREATE INDEX IF NOT EXISTS idx_passage_images_passage ON passage_images(passage_id); """) await db.commit() async def get_db() -> aiosqlite.Connection: db = await aiosqlite.connect(DB_PATH) db.row_factory = aiosqlite.Row await init_db(db) return db # --- Utøverprofiler --- async def upsert_athlete( db: aiosqlite.Connection, bib_number: str, name: str, club: Optional[str] = None, ) -> str: """Opprett eller oppdater utøver basert på startnummer. Returnerer profile_id.""" async with db.execute( "SELECT profile_id FROM athletes WHERE bib_number = ?", (bib_number,) ) as cur: row = await cur.fetchone() if row: profile_id = row["profile_id"] await db.execute( "UPDATE athletes SET name = ?, club = ? WHERE profile_id = ?", (name, club, profile_id), ) else: profile_id = str(uuid.uuid4()) await db.execute( "INSERT INTO athletes (profile_id, bib_number, name, club) VALUES (?, ?, ?, ?)", (profile_id, bib_number, name, club), ) await db.commit() return profile_id async def import_startlist_csv(db: aiosqlite.Connection, csv_content: str) -> dict: """ Importer startliste fra CSV-streng. Forventet kolonner: bib_number, name (og valgfritt: club) Returnerer {'imported': N, 'errors': [...]} """ reader = csv.DictReader(io.StringIO(csv_content)) imported = 0 errors = [] # Normaliser kolonnenavn (fjern whitespace, lowercase) fieldnames = [f.strip().lower() for f in (reader.fieldnames or [])] if "bib_number" not in fieldnames and "bib" not in fieldnames: return {"imported": 0, "errors": ["CSV mangler 'bib_number'-kolonne"]} if "name" not in fieldnames: return {"imported": 0, "errors": ["CSV mangler 'name'-kolonne"]} bib_col = "bib_number" if "bib_number" in fieldnames else "bib" for i, row in enumerate(reader, start=2): # Normaliser nøkler row = {k.strip().lower(): v.strip() for k, v in row.items()} bib = row.get(bib_col, "").strip() name = row.get("name", "").strip() club = row.get("club", "").strip() or None if not bib or not name: errors.append(f"Rad {i}: mangler bib eller navn") continue try: await upsert_athlete(db, bib, name, club) imported += 1 except Exception as e: errors.append(f"Rad {i}: {e}") return {"imported": imported, "errors": errors} async def get_athlete_by_bib(db: aiosqlite.Connection, bib: str) -> Optional[dict]: async with db.execute( "SELECT * FROM athletes WHERE bib_number = ?", (bib,) ) as cur: row = await cur.fetchone() return dict(row) if row else None async def list_athletes(db: aiosqlite.Connection) -> list[dict]: async with db.execute( "SELECT * FROM athletes ORDER BY CAST(bib_number AS INTEGER), bib_number" ) as cur: rows = await cur.fetchall() return [dict(r) for r in rows] async def delete_athlete(db: aiosqlite.Connection, profile_id: str) -> bool: cur = await db.execute( "DELETE FROM athletes WHERE profile_id = ?", (profile_id,) ) await db.commit() return cur.rowcount > 0 async def clear_startlist(db: aiosqlite.Connection) -> None: await db.execute("DELETE FROM athletes") await db.commit()