- races table: name, date, description, is_active
- stations table: ordered checkpoints with GPS per race
- New /api/races and /api/races/{id}/stations endpoints
- Upload now requires race + station selection; uses station GPS
so images without GPS EXIF are accepted
- passages filtered by active race throughout
- RacePage: create races, manage stations (add/edit/delete checkpoints)
- Navbar shows active race name
- Start and finish stations created automatically per race
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -14,6 +14,7 @@ import logging
|
||||
import shutil
|
||||
import uuid
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
|
||||
import aiosqlite
|
||||
from watchdog.events import FileSystemEventHandler, FileCreatedEvent
|
||||
@@ -125,6 +126,69 @@ async def process_image(path: Path) -> None:
|
||||
)
|
||||
|
||||
|
||||
async def process_image_with_override(
|
||||
path: Path,
|
||||
*,
|
||||
race_id: Optional[str],
|
||||
station_name: str,
|
||||
gps_lat: Optional[float],
|
||||
gps_lon: Optional[float],
|
||||
gps_alt: Optional[float],
|
||||
db,
|
||||
) -> None:
|
||||
"""
|
||||
Behandle bilde med manuelt oppgitt stasjon og GPS (fra web-opplasting).
|
||||
EXIF-tid brukes hvis tilgjengelig, ellers nåværende tidspunkt.
|
||||
"""
|
||||
from datetime import datetime, timezone
|
||||
from profile_db import get_athlete_by_bib
|
||||
|
||||
logger.info("Web-opplasting: %s → stasjon=%s", path.name, station_name)
|
||||
|
||||
# Forsøk EXIF for tidsstempel, fallback til nå
|
||||
try:
|
||||
meta = parse_image(path)
|
||||
timestamp = meta.timestamp_utc
|
||||
except ExifError:
|
||||
timestamp = datetime.now(timezone.utc)
|
||||
|
||||
ocr = read_bib(path)
|
||||
dest = _destination_path(path, timestamp)
|
||||
shutil.move(str(path), str(dest))
|
||||
|
||||
confidence = ocr.confidence
|
||||
needs_review = ocr.digits is None or confidence < MIN_AUTO_CONFIDENCE
|
||||
id_method = "bib_ocr" if not needs_review else "bib_ocr_uncertain"
|
||||
review_note = None if not needs_review else (
|
||||
"number_unreadable" if ocr.digits is None else "low_confidence"
|
||||
)
|
||||
|
||||
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"]
|
||||
|
||||
await log_passage(
|
||||
db,
|
||||
race_id=race_id,
|
||||
profile_id=profile_id,
|
||||
bib_number=ocr.digits,
|
||||
station=station_name,
|
||||
timestamp_utc=timestamp,
|
||||
gps_lat=gps_lat or 0.0,
|
||||
gps_lon=gps_lon or 0.0,
|
||||
gps_alt=gps_alt,
|
||||
confidence=confidence,
|
||||
proximity_score=ocr.proximity_score,
|
||||
id_method=id_method,
|
||||
source_image=str(dest),
|
||||
needs_review=needs_review,
|
||||
review_note=review_note,
|
||||
)
|
||||
logger.info("Passering logget: bib=%s station=%s", ocr.digits, station_name)
|
||||
|
||||
|
||||
async def process_existing() -> None:
|
||||
"""Behandle bilder som allerede ligger i depot/ ved oppstart."""
|
||||
for path in sorted(DEPOT_DIR.glob("*")):
|
||||
|
||||
Reference in New Issue
Block a user