From 018f84efd856e18cd54df69a3dca4dc746854ed1 Mon Sep 17 00:00:00 2001 From: steinhelge Date: Sat, 21 Mar 2026 17:51:03 +0100 Subject: [PATCH] Auto-create athlete in startlist for unrecognized bib numbers When a bib number is detected (via OCR or manual entry during review) but not found in the start list, it is now automatically added with the placeholder name "Ukjent #" instead of being left without a profile_id (which would exclude it from results). Co-Authored-By: Claude Sonnet 4.6 --- backend/api.py | 6 +++++- backend/ingest.py | 14 ++++---------- backend/profile_db.py | 8 ++++++++ frontend/src/pages/ReviewPage.jsx | 2 +- 4 files changed, 18 insertions(+), 12 deletions(-) diff --git a/backend/api.py b/backend/api.py index 45c039b..0ac0c6f 100644 --- a/backend/api.py +++ b/backend/api.py @@ -22,6 +22,7 @@ from profile_db import ( clear_startlist, delete_athlete, get_db, + get_or_create_athlete, import_startlist_csv, list_athletes, ) @@ -213,8 +214,11 @@ class ResolveRequest(BaseModel): @app.post("/api/passages/{passage_id}/resolve") async def resolve(passage_id: str, body: ResolveRequest, db=Depends(get_connection)): + profile_id = body.profile_id + if body.bib_number and not profile_id: + profile_id = await get_or_create_athlete(db, body.bib_number) ok = await resolve_passage(db, passage_id, - profile_id=body.profile_id, + profile_id=profile_id, bib_number=body.bib_number, review_note=body.review_note) if not ok: diff --git a/backend/ingest.py b/backend/ingest.py index c2021ba..6a2da61 100644 --- a/backend/ingest.py +++ b/backend/ingest.py @@ -23,7 +23,7 @@ from watchdog.observers import Observer from exif_parser import ExifError, parse_image from ocr import read_bib from passage_log import log_passage -from profile_db import get_athlete_by_bib, init_db +from profile_db import get_or_create_athlete, init_db logger = logging.getLogger(__name__) @@ -98,11 +98,7 @@ async def process_image(path: Path) -> None: await init_db(db) if bib_number and not needs_review: - athlete = await get_athlete_by_bib(db, bib_number) - if athlete: - profile_id = athlete["profile_id"] - else: - logger.debug("Ukjent startnummer: %s", bib_number) + profile_id = await get_or_create_athlete(db, bib_number) await log_passage( db, @@ -141,7 +137,7 @@ async def process_image_with_override( EXIF-tid brukes hvis tilgjengelig, ellers nåværende tidspunkt. """ from datetime import datetime, timezone - from profile_db import get_athlete_by_bib + from profile_db import get_or_create_athlete as _get_or_create logger.info("Web-opplasting: %s → stasjon=%s", path.name, station_name) @@ -165,9 +161,7 @@ async def process_image_with_override( profile_id = None if ocr.digits and not needs_review: - athlete = await get_athlete_by_bib(db, ocr.digits) - if athlete: - profile_id = athlete["profile_id"] + profile_id = await _get_or_create(db, ocr.digits) await log_passage( db, diff --git a/backend/profile_db.py b/backend/profile_db.py index 18da011..69e2502 100644 --- a/backend/profile_db.py +++ b/backend/profile_db.py @@ -150,6 +150,14 @@ async def import_startlist_csv(db: aiosqlite.Connection, csv_content: str) -> di return {"imported": imported, "errors": errors} +async def get_or_create_athlete(db: aiosqlite.Connection, bib_number: str) -> str: + """Hent eller opprett utøver basert på startnummer. Returnerer profile_id.""" + athlete = await get_athlete_by_bib(db, bib_number) + if athlete: + return athlete["profile_id"] + return await upsert_athlete(db, bib_number, f"Ukjent #{bib_number}") + + async def get_athlete_by_bib(db: aiosqlite.Connection, bib: str) -> Optional[dict]: async with db.execute( "SELECT * FROM athletes WHERE bib_number = ?", (bib,) diff --git a/frontend/src/pages/ReviewPage.jsx b/frontend/src/pages/ReviewPage.jsx index 0b250dc..fb2f96a 100644 --- a/frontend/src/pages/ReviewPage.jsx +++ b/frontend/src/pages/ReviewPage.jsx @@ -122,7 +122,7 @@ function ReviewCard({ passage, athletes, onResolved, onDeleted }) { )} {bib && !matchedAthlete && ( - Startnummer ikke i startliste + Ikke i startliste — legges til automatisk )}