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,119 @@
|
||||
"""
|
||||
Skriv og query passeringslogg i SQLite.
|
||||
"""
|
||||
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
from typing import Optional
|
||||
|
||||
import aiosqlite
|
||||
|
||||
|
||||
async def log_passage(
|
||||
db: aiosqlite.Connection,
|
||||
*,
|
||||
profile_id: Optional[str],
|
||||
bib_number: Optional[str],
|
||||
station: str,
|
||||
timestamp_utc: datetime,
|
||||
gps_lat: float,
|
||||
gps_lon: float,
|
||||
gps_alt: Optional[float],
|
||||
confidence: float,
|
||||
id_method: str,
|
||||
source_image: str,
|
||||
needs_review: bool = False,
|
||||
review_note: Optional[str] = None,
|
||||
) -> str:
|
||||
"""Logg én passering. Returnerer passage_id."""
|
||||
passage_id = str(uuid.uuid4())
|
||||
await db.execute(
|
||||
"""
|
||||
INSERT INTO passages (
|
||||
passage_id, profile_id, bib_number, station,
|
||||
timestamp_utc, gps_lat, gps_lon, gps_alt,
|
||||
confidence, id_method, source_image,
|
||||
needs_review, review_note
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
""",
|
||||
(
|
||||
passage_id,
|
||||
profile_id,
|
||||
bib_number,
|
||||
station,
|
||||
timestamp_utc.isoformat(),
|
||||
gps_lat,
|
||||
gps_lon,
|
||||
gps_alt,
|
||||
confidence,
|
||||
id_method,
|
||||
source_image,
|
||||
int(needs_review),
|
||||
review_note,
|
||||
),
|
||||
)
|
||||
await db.commit()
|
||||
return passage_id
|
||||
|
||||
|
||||
async def get_passages(
|
||||
db: aiosqlite.Connection,
|
||||
profile_id: Optional[str] = None,
|
||||
station: Optional[str] = None,
|
||||
needs_review: Optional[bool] = None,
|
||||
) -> list[dict]:
|
||||
"""Hent passeringer med valgfrie filtre."""
|
||||
clauses = []
|
||||
params = []
|
||||
|
||||
if profile_id is not None:
|
||||
clauses.append("p.profile_id = ?")
|
||||
params.append(profile_id)
|
||||
if station is not None:
|
||||
clauses.append("p.station = ?")
|
||||
params.append(station)
|
||||
if needs_review is not None:
|
||||
clauses.append("p.needs_review = ?")
|
||||
params.append(int(needs_review))
|
||||
|
||||
where = ("WHERE " + " AND ".join(clauses)) if clauses else ""
|
||||
|
||||
query = f"""
|
||||
SELECT p.*, a.name, a.club
|
||||
FROM passages p
|
||||
LEFT JOIN athletes a ON a.profile_id = p.profile_id
|
||||
{where}
|
||||
ORDER BY p.timestamp_utc
|
||||
"""
|
||||
async with db.execute(query, params) as cur:
|
||||
rows = await cur.fetchall()
|
||||
return [dict(r) for r in rows]
|
||||
|
||||
|
||||
async def resolve_passage(
|
||||
db: aiosqlite.Connection,
|
||||
passage_id: str,
|
||||
profile_id: Optional[str],
|
||||
bib_number: Optional[str],
|
||||
review_note: Optional[str] = None,
|
||||
) -> bool:
|
||||
"""Manuell oppdatering av en passering etter gjennomgang."""
|
||||
cur = await db.execute(
|
||||
"""
|
||||
UPDATE passages
|
||||
SET profile_id = ?, bib_number = ?, needs_review = 0,
|
||||
review_note = ?, id_method = 'manual'
|
||||
WHERE passage_id = ?
|
||||
""",
|
||||
(profile_id, bib_number, review_note, passage_id),
|
||||
)
|
||||
await db.commit()
|
||||
return cur.rowcount > 0
|
||||
|
||||
|
||||
async def delete_passage(db: aiosqlite.Connection, passage_id: str) -> bool:
|
||||
cur = await db.execute(
|
||||
"DELETE FROM passages WHERE passage_id = ?", (passage_id,)
|
||||
)
|
||||
await db.commit()
|
||||
return cur.rowcount > 0
|
||||
Reference in New Issue
Block a user