Migrasjonen er kjørt, og jeg har fortsatt helt til et komplett, verifisert API-lag.
Underveis fant og fikset jeg et reelt hull: round_participant manglet kolonner for selve rating-tallene (kun tee-navnet var lagret) — løst med migrasjon 021, kjørt mot ekte database sammen med resten.
Bygget og rullet ut:
app/routers/rounds.py — opprett/liste/hent/slett runde, legg til/fjern gjestedeltakere, registrer statistikk hull for hull, og en fullfør-endepunkt som kjører hele kjeden (Adjusted Gross Score → Score Differential → tellende-vurdering).
26 scratch-sjekker grønt, pluss en egen test mot den ekte kjørende teeoff-tjenesten som bekreftet at live-oppslaget fungerer nøyaktig som tiltenkt (ingen bane-kopi skrives noe sted).
Begge migrasjoner + ny kode er nå live mot ekte teecup_db/teecup_api. teeoff.no upåvirket.
Jeg la også inn /rounds og /personal-courses i frontendens proxy-oppsett proaktivt, siden prosjektet har blitt bitt av glemte proxy-ruter før — ikke deployet ennå, tas med neste gang frontend faktisk bygges.
Notatet om slaglengde-måling og avstand-til-punkter-på-banen er lagret i CLAUDE.md og FEATURE_BACKLOG.md, med en tydelig flagget avhengighet: dette krever geografiske/GPS-data som ingen nåværende kilde (verken teeoff eller den nye banekatalogen) har.
2026-07-22 11:31:08 +02:00
|
|
|
|
"""
|
|
|
|
|
|
Frittstående rundeføring med detaljert statistikk (ADR-033).
|
|
|
|
|
|
|
|
|
|
|
|
Eid av en BRUKER (`app_user.id`), ikke en organisasjon -- INGEN RLS på
|
|
|
|
|
|
`round`/`round_participant`/`round_hole`/`personal_course*` (Beslutning A).
|
|
|
|
|
|
Samme mønster som personlig profil/HCP-historikk/sekundær e-post: kun
|
|
|
|
|
|
`plain_connection()`, autorisasjon håndheves eksplisitt her med
|
|
|
|
|
|
`WHERE owner_user_id = $1` (eller via round_id -> owner-kjeden).
|
|
|
|
|
|
|
|
|
|
|
|
Banedata (Beslutning C): offisielle teeoff-baner slås opp LIVE ved hver
|
|
|
|
|
|
runde-opprettelse/deltaker-tilføyelse (ingen lokal kopi) via samme
|
|
|
|
|
|
`teeoff_client` som den org-scopede import-flyten i courses.py bruker --
|
|
|
|
|
|
men her INGEN `course`/`hole`/`tee`-rader skrives, kun et navn-snapshot
|
|
|
|
|
|
på selve runden og et rating-snapshot per deltaker.
|
|
|
|
|
|
|
|
|
|
|
|
v1-avgrensning (eksplisitt notert i ADR-033, ikke løst her): en deltaker
|
|
|
|
|
|
med `user_id` satt får IKKE egen tilgang til runden -- derfor støtter
|
|
|
|
|
|
DENNE runden av API-et kun GJEST-deltakere (fritekstnavn), ikke ekte
|
|
|
|
|
|
kontokobling. `round_participant.user_id`-kolonnen brukes fortsatt for
|
|
|
|
|
|
runde-EIEREN (alltid en ekte konto), bare ikke for andre i flighten ennå.
|
|
|
|
|
|
|
|
|
|
|
|
HCP-indeks-oppdatering (app_user.handicap_index/handicap_history) skjer
|
|
|
|
|
|
IKKE automatisk her -- eksplisitt uavklart punkt i ADR-033. Et fullført,
|
|
|
|
|
|
tellende resultat lagres kun som `round_participant.score_differential`.
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
|
|
from __future__ import annotations
|
|
|
|
|
|
|
2026-07-24 11:21:39 +02:00
|
|
|
|
import math
|
|
|
|
|
|
from datetime import date, datetime
|
Migrasjonen er kjørt, og jeg har fortsatt helt til et komplett, verifisert API-lag.
Underveis fant og fikset jeg et reelt hull: round_participant manglet kolonner for selve rating-tallene (kun tee-navnet var lagret) — løst med migrasjon 021, kjørt mot ekte database sammen med resten.
Bygget og rullet ut:
app/routers/rounds.py — opprett/liste/hent/slett runde, legg til/fjern gjestedeltakere, registrer statistikk hull for hull, og en fullfør-endepunkt som kjører hele kjeden (Adjusted Gross Score → Score Differential → tellende-vurdering).
26 scratch-sjekker grønt, pluss en egen test mot den ekte kjørende teeoff-tjenesten som bekreftet at live-oppslaget fungerer nøyaktig som tiltenkt (ingen bane-kopi skrives noe sted).
Begge migrasjoner + ny kode er nå live mot ekte teecup_db/teecup_api. teeoff.no upåvirket.
Jeg la også inn /rounds og /personal-courses i frontendens proxy-oppsett proaktivt, siden prosjektet har blitt bitt av glemte proxy-ruter før — ikke deployet ennå, tas med neste gang frontend faktisk bygges.
Notatet om slaglengde-måling og avstand-til-punkter-på-banen er lagret i CLAUDE.md og FEATURE_BACKLOG.md, med en tydelig flagget avhengighet: dette krever geografiske/GPS-data som ingen nåværende kilde (verken teeoff eller den nye banekatalogen) har.
2026-07-22 11:31:08 +02:00
|
|
|
|
from typing import Literal
|
|
|
|
|
|
|
2026-07-26 08:49:37 +02:00
|
|
|
|
from fastapi import APIRouter, Depends, HTTPException, Query, WebSocket, WebSocketDisconnect
|
2026-07-26 09:42:06 +02:00
|
|
|
|
from pydantic import BaseModel, EmailStr, Field
|
Migrasjonen er kjørt, og jeg har fortsatt helt til et komplett, verifisert API-lag.
Underveis fant og fikset jeg et reelt hull: round_participant manglet kolonner for selve rating-tallene (kun tee-navnet var lagret) — løst med migrasjon 021, kjørt mot ekte database sammen med resten.
Bygget og rullet ut:
app/routers/rounds.py — opprett/liste/hent/slett runde, legg til/fjern gjestedeltakere, registrer statistikk hull for hull, og en fullfør-endepunkt som kjører hele kjeden (Adjusted Gross Score → Score Differential → tellende-vurdering).
26 scratch-sjekker grønt, pluss en egen test mot den ekte kjørende teeoff-tjenesten som bekreftet at live-oppslaget fungerer nøyaktig som tiltenkt (ingen bane-kopi skrives noe sted).
Begge migrasjoner + ny kode er nå live mot ekte teecup_db/teecup_api. teeoff.no upåvirket.
Jeg la også inn /rounds og /personal-courses i frontendens proxy-oppsett proaktivt, siden prosjektet har blitt bitt av glemte proxy-ruter før — ikke deployet ennå, tas med neste gang frontend faktisk bygges.
Notatet om slaglengde-måling og avstand-til-punkter-på-banen er lagret i CLAUDE.md og FEATURE_BACKLOG.md, med en tydelig flagget avhengighet: dette krever geografiske/GPS-data som ingen nåværende kilde (verken teeoff eller den nye banekatalogen) har.
2026-07-22 11:31:08 +02:00
|
|
|
|
|
|
|
|
|
|
from .. import teeoff_client
|
2026-07-26 08:49:37 +02:00
|
|
|
|
from ..auth import CurrentUser, get_current_user, get_current_user_from_websocket
|
Migrasjonen er kjørt, og jeg har fortsatt helt til et komplett, verifisert API-lag.
Underveis fant og fikset jeg et reelt hull: round_participant manglet kolonner for selve rating-tallene (kun tee-navnet var lagret) — løst med migrasjon 021, kjørt mot ekte database sammen med resten.
Bygget og rullet ut:
app/routers/rounds.py — opprett/liste/hent/slett runde, legg til/fjern gjestedeltakere, registrer statistikk hull for hull, og en fullfør-endepunkt som kjører hele kjeden (Adjusted Gross Score → Score Differential → tellende-vurdering).
26 scratch-sjekker grønt, pluss en egen test mot den ekte kjørende teeoff-tjenesten som bekreftet at live-oppslaget fungerer nøyaktig som tiltenkt (ingen bane-kopi skrives noe sted).
Begge migrasjoner + ny kode er nå live mot ekte teecup_db/teecup_api. teeoff.no upåvirket.
Jeg la også inn /rounds og /personal-courses i frontendens proxy-oppsett proaktivt, siden prosjektet har blitt bitt av glemte proxy-ruter før — ikke deployet ennå, tas med neste gang frontend faktisk bygges.
Notatet om slaglengde-måling og avstand-til-punkter-på-banen er lagret i CLAUDE.md og FEATURE_BACKLOG.md, med en tydelig flagget avhengighet: dette krever geografiske/GPS-data som ingen nåværende kilde (verken teeoff eller den nye banekatalogen) har.
2026-07-22 11:31:08 +02:00
|
|
|
|
from ..db import plain_connection
|
|
|
|
|
|
from ..errors import app_error, translate_db_errors
|
2026-07-26 08:49:37 +02:00
|
|
|
|
from ..realtime import broadcast_round_update, live_sockets_for_round
|
Migrasjonen er kjørt, og jeg har fortsatt helt til et komplett, verifisert API-lag.
Underveis fant og fikset jeg et reelt hull: round_participant manglet kolonner for selve rating-tallene (kun tee-navnet var lagret) — løst med migrasjon 021, kjørt mot ekte database sammen med resten.
Bygget og rullet ut:
app/routers/rounds.py — opprett/liste/hent/slett runde, legg til/fjern gjestedeltakere, registrer statistikk hull for hull, og en fullfør-endepunkt som kjører hele kjeden (Adjusted Gross Score → Score Differential → tellende-vurdering).
26 scratch-sjekker grønt, pluss en egen test mot den ekte kjørende teeoff-tjenesten som bekreftet at live-oppslaget fungerer nøyaktig som tiltenkt (ingen bane-kopi skrives noe sted).
Begge migrasjoner + ny kode er nå live mot ekte teecup_db/teecup_api. teeoff.no upåvirket.
Jeg la også inn /rounds og /personal-courses i frontendens proxy-oppsett proaktivt, siden prosjektet har blitt bitt av glemte proxy-ruter før — ikke deployet ennå, tas med neste gang frontend faktisk bygges.
Notatet om slaglengde-måling og avstand-til-punkter-på-banen er lagret i CLAUDE.md og FEATURE_BACKLOG.md, med en tydelig flagget avhengighet: dette krever geografiske/GPS-data som ingen nåværende kilde (verken teeoff eller den nye banekatalogen) har.
2026-07-22 11:31:08 +02:00
|
|
|
|
from handicap_engine import (
|
|
|
|
|
|
adjusted_gross_score,
|
|
|
|
|
|
allocate_strokes_by_index,
|
|
|
|
|
|
course_handicap,
|
|
|
|
|
|
round_counts_for_handicap,
|
|
|
|
|
|
score_differential,
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
router = APIRouter(tags=["rounds"])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
# Global banekatalog for egendefinerte (ikke-teeoff) baner
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
|
|
class PersonalCourseHoleIn(BaseModel):
|
|
|
|
|
|
hole_number: int = Field(ge=1, le=18)
|
|
|
|
|
|
par: int = Field(ge=3, le=6)
|
|
|
|
|
|
stroke_index: int = Field(ge=1, le=18)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class PersonalCourseTeeRatingIn(BaseModel):
|
|
|
|
|
|
gender: Literal["m", "f"]
|
|
|
|
|
|
course_rating: float
|
|
|
|
|
|
slope_rating: int = Field(ge=55, le=155)
|
|
|
|
|
|
par: int
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class PersonalCourseTeeIn(BaseModel):
|
|
|
|
|
|
name: str
|
|
|
|
|
|
ratings: list[PersonalCourseTeeRatingIn] = Field(min_length=1, max_length=2)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class PersonalCourseCreate(BaseModel):
|
|
|
|
|
|
name: str
|
|
|
|
|
|
holes: list[PersonalCourseHoleIn] = Field(min_length=18, max_length=18)
|
|
|
|
|
|
tees: list[PersonalCourseTeeIn] = Field(min_length=1)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class PersonalCourseOut(BaseModel):
|
|
|
|
|
|
id: str
|
|
|
|
|
|
name: str
|
|
|
|
|
|
|
|
|
|
|
|
|
Update Todos
Add non-org-scoped teeoff search + personal-course detail endpoints to rounds.py
Build /rounds list page + component
Build /rounds/new creation flow (course search/create, tee, date, holes)
Build /rounds/[id] detail page (participants, hole-by-hole stats, complete)
Link new feature from dashboard
Scratch-verify backend additions + typecheck production build
Deploy live after user confirmation
Update ADR-033/FEATURE_BACKLOG/CLAUDE.md status
Frontend for standalone round tracking is built and fully verified in scratch — 22 backend contract checks plus a live end-to-end test against both course sources (custom bane and real teeoff lookup), and a clean typechecked production build (docker build --target builder) showing all new routes (/rounds, /rounds/new, /rounds/[id]).
What's new, in summary:
Backend: three additions to app/routers/rounds.py — non-org-scoped teeoff facility/course search (/rounds/official-search*), a personal-course detail endpoint exposing tee/gender info, and a GET .../holes endpoint (needed since RoundOut never carried hole-level data). Also changed the hole-PATCH endpoint to return the full updated row instead of {"ok": true}. No migration — no schema touched.
Frontend: /rounds (list), /rounds/new (course search or create, tee/date/holes-planned), /rounds/[id] (participants, hole-by-hole stat entry with GIR auto-derived, complete-round flow with HCP differential summary). Linked from the dashboard both as a header shortcut and a prominent "Egne runder" entry card.
One thing worth flagging: the hole-PATCH endpoint replaces all fields every call rather than doing a true partial update, so the frontend always merges the current hole state before sending a patch — I confirmed this contract explicitly in scratch (a naive partial PATCH silently nulls out unrelated fields).
2026-07-23 05:51:26 +02:00
|
|
|
|
class TeeOption(BaseModel):
|
|
|
|
|
|
name: str
|
|
|
|
|
|
genders: list[Literal["m", "f"]]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class PersonalCourseDetail(BaseModel):
|
|
|
|
|
|
id: str
|
|
|
|
|
|
name: str
|
|
|
|
|
|
tees: list[TeeOption]
|
|
|
|
|
|
|
|
|
|
|
|
|
Migrasjonen er kjørt, og jeg har fortsatt helt til et komplett, verifisert API-lag.
Underveis fant og fikset jeg et reelt hull: round_participant manglet kolonner for selve rating-tallene (kun tee-navnet var lagret) — løst med migrasjon 021, kjørt mot ekte database sammen med resten.
Bygget og rullet ut:
app/routers/rounds.py — opprett/liste/hent/slett runde, legg til/fjern gjestedeltakere, registrer statistikk hull for hull, og en fullfør-endepunkt som kjører hele kjeden (Adjusted Gross Score → Score Differential → tellende-vurdering).
26 scratch-sjekker grønt, pluss en egen test mot den ekte kjørende teeoff-tjenesten som bekreftet at live-oppslaget fungerer nøyaktig som tiltenkt (ingen bane-kopi skrives noe sted).
Begge migrasjoner + ny kode er nå live mot ekte teecup_db/teecup_api. teeoff.no upåvirket.
Jeg la også inn /rounds og /personal-courses i frontendens proxy-oppsett proaktivt, siden prosjektet har blitt bitt av glemte proxy-ruter før — ikke deployet ennå, tas med neste gang frontend faktisk bygges.
Notatet om slaglengde-måling og avstand-til-punkter-på-banen er lagret i CLAUDE.md og FEATURE_BACKLOG.md, med en tydelig flagget avhengighet: dette krever geografiske/GPS-data som ingen nåværende kilde (verken teeoff eller den nye banekatalogen) har.
2026-07-22 11:31:08 +02:00
|
|
|
|
@router.get("/personal-courses", response_model=list[PersonalCourseOut])
|
|
|
|
|
|
async def search_personal_courses(
|
|
|
|
|
|
q: str = "",
|
|
|
|
|
|
user: CurrentUser = Depends(get_current_user),
|
|
|
|
|
|
) -> list[PersonalCourseOut]:
|
|
|
|
|
|
async with plain_connection() as conn:
|
|
|
|
|
|
rows = await conn.fetch(
|
|
|
|
|
|
"SELECT id::text AS id, name FROM personal_course WHERE name ILIKE $1 ORDER BY name LIMIT 20",
|
|
|
|
|
|
f"%{q.strip()}%",
|
|
|
|
|
|
)
|
|
|
|
|
|
return [PersonalCourseOut(**dict(r)) for r in rows]
|
|
|
|
|
|
|
|
|
|
|
|
|
Update Todos
Add non-org-scoped teeoff search + personal-course detail endpoints to rounds.py
Build /rounds list page + component
Build /rounds/new creation flow (course search/create, tee, date, holes)
Build /rounds/[id] detail page (participants, hole-by-hole stats, complete)
Link new feature from dashboard
Scratch-verify backend additions + typecheck production build
Deploy live after user confirmation
Update ADR-033/FEATURE_BACKLOG/CLAUDE.md status
Frontend for standalone round tracking is built and fully verified in scratch — 22 backend contract checks plus a live end-to-end test against both course sources (custom bane and real teeoff lookup), and a clean typechecked production build (docker build --target builder) showing all new routes (/rounds, /rounds/new, /rounds/[id]).
What's new, in summary:
Backend: three additions to app/routers/rounds.py — non-org-scoped teeoff facility/course search (/rounds/official-search*), a personal-course detail endpoint exposing tee/gender info, and a GET .../holes endpoint (needed since RoundOut never carried hole-level data). Also changed the hole-PATCH endpoint to return the full updated row instead of {"ok": true}. No migration — no schema touched.
Frontend: /rounds (list), /rounds/new (course search or create, tee/date/holes-planned), /rounds/[id] (participants, hole-by-hole stat entry with GIR auto-derived, complete-round flow with HCP differential summary). Linked from the dashboard both as a header shortcut and a prominent "Egne runder" entry card.
One thing worth flagging: the hole-PATCH endpoint replaces all fields every call rather than doing a true partial update, so the frontend always merges the current hole state before sending a patch — I confirmed this contract explicitly in scratch (a naive partial PATCH silently nulls out unrelated fields).
2026-07-23 05:51:26 +02:00
|
|
|
|
@router.get("/personal-courses/{personal_course_id}", response_model=PersonalCourseDetail)
|
|
|
|
|
|
async def get_personal_course(
|
|
|
|
|
|
personal_course_id: str,
|
|
|
|
|
|
user: CurrentUser = Depends(get_current_user),
|
|
|
|
|
|
) -> PersonalCourseDetail:
|
|
|
|
|
|
async with plain_connection() as conn:
|
|
|
|
|
|
course_row = await conn.fetchrow("SELECT id::text AS id, name FROM personal_course WHERE id = $1", personal_course_id)
|
|
|
|
|
|
if course_row is None:
|
|
|
|
|
|
raise app_error(404, "NOT_FOUND", "Den egendefinerte banen finnes ikke.")
|
|
|
|
|
|
tee_rows = await conn.fetch(
|
|
|
|
|
|
"""
|
|
|
|
|
|
SELECT t.name AS tee_name, r.gender
|
|
|
|
|
|
FROM personal_course_tee t
|
|
|
|
|
|
JOIN personal_course_tee_rating r ON r.personal_course_tee_id = t.id
|
|
|
|
|
|
WHERE t.personal_course_id = $1
|
|
|
|
|
|
ORDER BY t.name
|
|
|
|
|
|
""",
|
|
|
|
|
|
personal_course_id,
|
|
|
|
|
|
)
|
|
|
|
|
|
tees: dict[str, list[str]] = {}
|
|
|
|
|
|
for r in tee_rows:
|
|
|
|
|
|
tees.setdefault(r["tee_name"], []).append(r["gender"])
|
|
|
|
|
|
return PersonalCourseDetail(
|
|
|
|
|
|
id=course_row["id"],
|
|
|
|
|
|
name=course_row["name"],
|
|
|
|
|
|
tees=[TeeOption(name=name, genders=genders) for name, genders in tees.items()],
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
Migrasjonen er kjørt, og jeg har fortsatt helt til et komplett, verifisert API-lag.
Underveis fant og fikset jeg et reelt hull: round_participant manglet kolonner for selve rating-tallene (kun tee-navnet var lagret) — løst med migrasjon 021, kjørt mot ekte database sammen med resten.
Bygget og rullet ut:
app/routers/rounds.py — opprett/liste/hent/slett runde, legg til/fjern gjestedeltakere, registrer statistikk hull for hull, og en fullfør-endepunkt som kjører hele kjeden (Adjusted Gross Score → Score Differential → tellende-vurdering).
26 scratch-sjekker grønt, pluss en egen test mot den ekte kjørende teeoff-tjenesten som bekreftet at live-oppslaget fungerer nøyaktig som tiltenkt (ingen bane-kopi skrives noe sted).
Begge migrasjoner + ny kode er nå live mot ekte teecup_db/teecup_api. teeoff.no upåvirket.
Jeg la også inn /rounds og /personal-courses i frontendens proxy-oppsett proaktivt, siden prosjektet har blitt bitt av glemte proxy-ruter før — ikke deployet ennå, tas med neste gang frontend faktisk bygges.
Notatet om slaglengde-måling og avstand-til-punkter-på-banen er lagret i CLAUDE.md og FEATURE_BACKLOG.md, med en tydelig flagget avhengighet: dette krever geografiske/GPS-data som ingen nåværende kilde (verken teeoff eller den nye banekatalogen) har.
2026-07-22 11:31:08 +02:00
|
|
|
|
@router.post("/personal-courses", response_model=PersonalCourseOut, status_code=201)
|
|
|
|
|
|
async def create_personal_course(
|
|
|
|
|
|
body: PersonalCourseCreate,
|
|
|
|
|
|
user: CurrentUser = Depends(get_current_user),
|
|
|
|
|
|
) -> PersonalCourseOut:
|
|
|
|
|
|
numbers = sorted(h.hole_number for h in body.holes)
|
|
|
|
|
|
indexes = sorted(h.stroke_index for h in body.holes)
|
|
|
|
|
|
if numbers != list(range(1, 19)) or indexes != list(range(1, 19)):
|
|
|
|
|
|
raise app_error(400, "VALIDATION_FAILED", "Alle 18 hullnumre og alle 18 stroke-indekser må være unike, 1-18.")
|
|
|
|
|
|
|
|
|
|
|
|
async with plain_connection() as conn, translate_db_errors():
|
|
|
|
|
|
async with conn.transaction():
|
|
|
|
|
|
course_row = await conn.fetchrow(
|
|
|
|
|
|
"INSERT INTO personal_course (name, created_by_user_id) VALUES ($1, $2) RETURNING id::text AS id, name",
|
|
|
|
|
|
body.name.strip(),
|
|
|
|
|
|
user.user_id,
|
|
|
|
|
|
)
|
|
|
|
|
|
for h in body.holes:
|
|
|
|
|
|
await conn.execute(
|
|
|
|
|
|
"INSERT INTO personal_course_hole (personal_course_id, hole_number, par, stroke_index) VALUES ($1, $2, $3, $4)",
|
|
|
|
|
|
course_row["id"],
|
|
|
|
|
|
h.hole_number,
|
|
|
|
|
|
h.par,
|
|
|
|
|
|
h.stroke_index,
|
|
|
|
|
|
)
|
|
|
|
|
|
for t in body.tees:
|
|
|
|
|
|
tee_row = await conn.fetchrow(
|
|
|
|
|
|
"INSERT INTO personal_course_tee (personal_course_id, name) VALUES ($1, $2) RETURNING id",
|
|
|
|
|
|
course_row["id"],
|
|
|
|
|
|
t.name.strip(),
|
|
|
|
|
|
)
|
|
|
|
|
|
for r in t.ratings:
|
|
|
|
|
|
await conn.execute(
|
|
|
|
|
|
"""
|
|
|
|
|
|
INSERT INTO personal_course_tee_rating
|
|
|
|
|
|
(personal_course_tee_id, gender, course_rating, slope_rating, par)
|
|
|
|
|
|
VALUES ($1, $2, $3, $4, $5)
|
|
|
|
|
|
""",
|
|
|
|
|
|
tee_row["id"],
|
|
|
|
|
|
r.gender,
|
|
|
|
|
|
r.course_rating,
|
|
|
|
|
|
r.slope_rating,
|
|
|
|
|
|
r.par,
|
|
|
|
|
|
)
|
|
|
|
|
|
return PersonalCourseOut(**dict(course_row))
|
|
|
|
|
|
|
|
|
|
|
|
|
Update Todos
Add non-org-scoped teeoff search + personal-course detail endpoints to rounds.py
Build /rounds list page + component
Build /rounds/new creation flow (course search/create, tee, date, holes)
Build /rounds/[id] detail page (participants, hole-by-hole stats, complete)
Link new feature from dashboard
Scratch-verify backend additions + typecheck production build
Deploy live after user confirmation
Update ADR-033/FEATURE_BACKLOG/CLAUDE.md status
Frontend for standalone round tracking is built and fully verified in scratch — 22 backend contract checks plus a live end-to-end test against both course sources (custom bane and real teeoff lookup), and a clean typechecked production build (docker build --target builder) showing all new routes (/rounds, /rounds/new, /rounds/[id]).
What's new, in summary:
Backend: three additions to app/routers/rounds.py — non-org-scoped teeoff facility/course search (/rounds/official-search*), a personal-course detail endpoint exposing tee/gender info, and a GET .../holes endpoint (needed since RoundOut never carried hole-level data). Also changed the hole-PATCH endpoint to return the full updated row instead of {"ok": true}. No migration — no schema touched.
Frontend: /rounds (list), /rounds/new (course search or create, tee/date/holes-planned), /rounds/[id] (participants, hole-by-hole stat entry with GIR auto-derived, complete-round flow with HCP differential summary). Linked from the dashboard both as a header shortcut and a prominent "Egne runder" entry card.
One thing worth flagging: the hole-PATCH endpoint replaces all fields every call rather than doing a true partial update, so the frontend always merges the current hole state before sending a patch — I confirmed this contract explicitly in scratch (a naive partial PATCH silently nulls out unrelated fields).
2026-07-23 05:51:26 +02:00
|
|
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
# Offisiell bane fra teeoff (ADR-019/ADR-033 Beslutning C) -- IKKE org-scopet,
|
|
|
|
|
|
# ulikt courses.py sine tilsvarende endepunkter (frittstående runder har
|
|
|
|
|
|
# ingen organisasjon). Rent lese-søk, ingen import/persistering av banen.
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
|
|
class OfficialFacility(BaseModel):
|
|
|
|
|
|
slug: str
|
|
|
|
|
|
name: str
|
|
|
|
|
|
city: str | None
|
|
|
|
|
|
county: str | None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class OfficialCourseOption(BaseModel):
|
|
|
|
|
|
teeoff_course_id: int
|
|
|
|
|
|
name: str
|
|
|
|
|
|
is_main_course: bool
|
|
|
|
|
|
tees: list[TeeOption]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class OfficialFacilityDetail(BaseModel):
|
|
|
|
|
|
slug: str
|
|
|
|
|
|
name: str
|
|
|
|
|
|
courses: list[OfficialCourseOption]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.get("/rounds/official-search", response_model=list[OfficialFacility])
|
|
|
|
|
|
async def search_official_courses_for_round(
|
|
|
|
|
|
q: str = "",
|
|
|
|
|
|
user: CurrentUser = Depends(get_current_user),
|
|
|
|
|
|
) -> list[OfficialFacility]:
|
|
|
|
|
|
try:
|
|
|
|
|
|
facilities = await teeoff_client.search_facilities(q)
|
|
|
|
|
|
except teeoff_client.TeeoffUnavailableError:
|
|
|
|
|
|
raise app_error(502, "EXTERNAL_SERVICE_UNAVAILABLE", "Klarte ikke å hente baner fra teeoff akkurat nå.")
|
|
|
|
|
|
return [
|
|
|
|
|
|
OfficialFacility(slug=f["slug"], name=f["name"], city=f.get("city"), county=f.get("county"))
|
|
|
|
|
|
for f in facilities
|
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-07-24 11:21:39 +02:00
|
|
|
|
class NearbyFacility(OfficialFacility):
|
|
|
|
|
|
distance_km: float
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _haversine_km(lat1: float, lng1: float, lat2: float, lng2: float) -> float:
|
|
|
|
|
|
r = 6371.0
|
|
|
|
|
|
phi1, phi2 = math.radians(lat1), math.radians(lat2)
|
|
|
|
|
|
dphi = math.radians(lat2 - lat1)
|
|
|
|
|
|
dlambda = math.radians(lng2 - lng1)
|
|
|
|
|
|
a = math.sin(dphi / 2) ** 2 + math.cos(phi1) * math.cos(phi2) * math.sin(dlambda / 2) ** 2
|
|
|
|
|
|
return 2 * r * math.asin(math.sqrt(a))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# MERK: MÅ registreres FØR /rounds/official-search/{slug} under, ellers
|
|
|
|
|
|
# tolker FastAPI "nearby" som en ugyldig slug-verdi til den ruten (samme
|
|
|
|
|
|
# lærdom som ADR-020s "by-code" måtte registreres før {tournament_id}).
|
|
|
|
|
|
@router.get("/rounds/official-search/nearby", response_model=list[NearbyFacility])
|
|
|
|
|
|
async def nearby_official_courses_for_round(
|
|
|
|
|
|
lat: float = Query(...),
|
|
|
|
|
|
lng: float = Query(...),
|
|
|
|
|
|
limit: int = Query(default=5, ge=1, le=20),
|
|
|
|
|
|
user: CurrentUser = Depends(get_current_user),
|
|
|
|
|
|
) -> list[NearbyFacility]:
|
|
|
|
|
|
try:
|
|
|
|
|
|
facilities = await teeoff_client.search_facilities("")
|
|
|
|
|
|
except teeoff_client.TeeoffUnavailableError:
|
|
|
|
|
|
raise app_error(502, "EXTERNAL_SERVICE_UNAVAILABLE", "Klarte ikke å hente baner fra teeoff akkurat nå.")
|
|
|
|
|
|
with_coords = [f for f in facilities if f.get("lat") is not None and f.get("lng") is not None]
|
|
|
|
|
|
scored = sorted(with_coords, key=lambda f: _haversine_km(lat, lng, f["lat"], f["lng"]))
|
|
|
|
|
|
return [
|
|
|
|
|
|
NearbyFacility(
|
|
|
|
|
|
slug=f["slug"], name=f["name"], city=f.get("city"), county=f.get("county"),
|
|
|
|
|
|
distance_km=round(_haversine_km(lat, lng, f["lat"], f["lng"]), 1),
|
|
|
|
|
|
)
|
|
|
|
|
|
for f in scored[:limit]
|
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
|
|
|
Update Todos
Add non-org-scoped teeoff search + personal-course detail endpoints to rounds.py
Build /rounds list page + component
Build /rounds/new creation flow (course search/create, tee, date, holes)
Build /rounds/[id] detail page (participants, hole-by-hole stats, complete)
Link new feature from dashboard
Scratch-verify backend additions + typecheck production build
Deploy live after user confirmation
Update ADR-033/FEATURE_BACKLOG/CLAUDE.md status
Frontend for standalone round tracking is built and fully verified in scratch — 22 backend contract checks plus a live end-to-end test against both course sources (custom bane and real teeoff lookup), and a clean typechecked production build (docker build --target builder) showing all new routes (/rounds, /rounds/new, /rounds/[id]).
What's new, in summary:
Backend: three additions to app/routers/rounds.py — non-org-scoped teeoff facility/course search (/rounds/official-search*), a personal-course detail endpoint exposing tee/gender info, and a GET .../holes endpoint (needed since RoundOut never carried hole-level data). Also changed the hole-PATCH endpoint to return the full updated row instead of {"ok": true}. No migration — no schema touched.
Frontend: /rounds (list), /rounds/new (course search or create, tee/date/holes-planned), /rounds/[id] (participants, hole-by-hole stat entry with GIR auto-derived, complete-round flow with HCP differential summary). Linked from the dashboard both as a header shortcut and a prominent "Egne runder" entry card.
One thing worth flagging: the hole-PATCH endpoint replaces all fields every call rather than doing a true partial update, so the frontend always merges the current hole state before sending a patch — I confirmed this contract explicitly in scratch (a naive partial PATCH silently nulls out unrelated fields).
2026-07-23 05:51:26 +02:00
|
|
|
|
@router.get("/rounds/official-search/{slug}", response_model=OfficialFacilityDetail)
|
|
|
|
|
|
async def get_official_facility_for_round(
|
|
|
|
|
|
slug: str,
|
|
|
|
|
|
user: CurrentUser = Depends(get_current_user),
|
|
|
|
|
|
) -> OfficialFacilityDetail:
|
|
|
|
|
|
try:
|
|
|
|
|
|
facility = await teeoff_client.get_facility(slug)
|
|
|
|
|
|
except teeoff_client.TeeoffNotFoundError:
|
|
|
|
|
|
raise app_error(404, "NOT_FOUND", "Anlegget finnes ikke i teeoff.")
|
|
|
|
|
|
except teeoff_client.TeeoffUnavailableError:
|
|
|
|
|
|
raise app_error(502, "EXTERNAL_SERVICE_UNAVAILABLE", "Klarte ikke å hente baneinfo fra teeoff akkurat nå.")
|
|
|
|
|
|
|
|
|
|
|
|
courses: list[OfficialCourseOption] = []
|
|
|
|
|
|
for c in facility.get("courses", []):
|
|
|
|
|
|
if len(c.get("holes") or []) != 18:
|
|
|
|
|
|
continue
|
|
|
|
|
|
tees: list[TeeOption] = []
|
|
|
|
|
|
for t in c.get("tees") or []:
|
|
|
|
|
|
genders: list[str] = []
|
|
|
|
|
|
if t.get("cr_men") is not None and t.get("slope_men") is not None:
|
|
|
|
|
|
genders.append("m")
|
|
|
|
|
|
if t.get("cr_women") is not None and t.get("slope_women") is not None:
|
|
|
|
|
|
genders.append("f")
|
|
|
|
|
|
if genders:
|
|
|
|
|
|
tees.append(TeeOption(name=t.get("name") or "Tee", genders=genders))
|
|
|
|
|
|
courses.append(
|
|
|
|
|
|
OfficialCourseOption(
|
|
|
|
|
|
teeoff_course_id=c["id"],
|
|
|
|
|
|
name=c["name"],
|
|
|
|
|
|
is_main_course=bool(c.get("is_main_course")),
|
|
|
|
|
|
tees=tees,
|
|
|
|
|
|
)
|
|
|
|
|
|
)
|
|
|
|
|
|
return OfficialFacilityDetail(slug=facility["slug"], name=facility["name"], courses=courses)
|
|
|
|
|
|
|
|
|
|
|
|
|
Migrasjonen er kjørt, og jeg har fortsatt helt til et komplett, verifisert API-lag.
Underveis fant og fikset jeg et reelt hull: round_participant manglet kolonner for selve rating-tallene (kun tee-navnet var lagret) — løst med migrasjon 021, kjørt mot ekte database sammen med resten.
Bygget og rullet ut:
app/routers/rounds.py — opprett/liste/hent/slett runde, legg til/fjern gjestedeltakere, registrer statistikk hull for hull, og en fullfør-endepunkt som kjører hele kjeden (Adjusted Gross Score → Score Differential → tellende-vurdering).
26 scratch-sjekker grønt, pluss en egen test mot den ekte kjørende teeoff-tjenesten som bekreftet at live-oppslaget fungerer nøyaktig som tiltenkt (ingen bane-kopi skrives noe sted).
Begge migrasjoner + ny kode er nå live mot ekte teecup_db/teecup_api. teeoff.no upåvirket.
Jeg la også inn /rounds og /personal-courses i frontendens proxy-oppsett proaktivt, siden prosjektet har blitt bitt av glemte proxy-ruter før — ikke deployet ennå, tas med neste gang frontend faktisk bygges.
Notatet om slaglengde-måling og avstand-til-punkter-på-banen er lagret i CLAUDE.md og FEATURE_BACKLOG.md, med en tydelig flagget avhengighet: dette krever geografiske/GPS-data som ingen nåværende kilde (verken teeoff eller den nye banekatalogen) har.
2026-07-22 11:31:08 +02:00
|
|
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
# Banedata-oppslag (delt mellom opprett-runde og legg-til-deltaker)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
#
|
|
|
|
|
|
# Returnerer (par_per_hole, stroke_index_per_hole, tee_names) og en
|
|
|
|
|
|
# funksjon for å slå opp (course_rating, slope_rating, par) for en gitt
|
|
|
|
|
|
# tee+kjønn -- felles for BÅDE teeoff (live) og personal_course (lagret).
|
|
|
|
|
|
|
|
|
|
|
|
class _ResolvedCourse:
|
|
|
|
|
|
def __init__(self, holes: list[tuple[int, int, int]], course_name: str):
|
|
|
|
|
|
# holes: liste av (hole_number, par, stroke_index), sortert 1..18
|
|
|
|
|
|
self.holes = holes
|
|
|
|
|
|
self.course_name = course_name
|
|
|
|
|
|
self._ratings: dict[tuple[str, str], tuple[float, int, int]] = {}
|
|
|
|
|
|
|
|
|
|
|
|
def add_rating(self, tee_name: str, gender: str, course_rating: float, slope_rating: int, par: int) -> None:
|
|
|
|
|
|
self._ratings[(tee_name, gender)] = (course_rating, slope_rating, par)
|
|
|
|
|
|
|
|
|
|
|
|
def rating_for(self, tee_name: str, gender: str) -> tuple[float, int, int] | None:
|
|
|
|
|
|
return self._ratings.get((tee_name, gender))
|
|
|
|
|
|
|
2026-07-26 09:18:52 +02:00
|
|
|
|
def tee_options(self) -> list["TeeOption"]:
|
|
|
|
|
|
names: dict[str, list[Literal["m", "f"]]] = {}
|
|
|
|
|
|
for tee_name, gender in self._ratings:
|
|
|
|
|
|
names.setdefault(tee_name, []).append(gender) # type: ignore[arg-type]
|
|
|
|
|
|
return [TeeOption(name=n, genders=sorted(g)) for n, g in names.items()]
|
|
|
|
|
|
|
Migrasjonen er kjørt, og jeg har fortsatt helt til et komplett, verifisert API-lag.
Underveis fant og fikset jeg et reelt hull: round_participant manglet kolonner for selve rating-tallene (kun tee-navnet var lagret) — løst med migrasjon 021, kjørt mot ekte database sammen med resten.
Bygget og rullet ut:
app/routers/rounds.py — opprett/liste/hent/slett runde, legg til/fjern gjestedeltakere, registrer statistikk hull for hull, og en fullfør-endepunkt som kjører hele kjeden (Adjusted Gross Score → Score Differential → tellende-vurdering).
26 scratch-sjekker grønt, pluss en egen test mot den ekte kjørende teeoff-tjenesten som bekreftet at live-oppslaget fungerer nøyaktig som tiltenkt (ingen bane-kopi skrives noe sted).
Begge migrasjoner + ny kode er nå live mot ekte teecup_db/teecup_api. teeoff.no upåvirket.
Jeg la også inn /rounds og /personal-courses i frontendens proxy-oppsett proaktivt, siden prosjektet har blitt bitt av glemte proxy-ruter før — ikke deployet ennå, tas med neste gang frontend faktisk bygges.
Notatet om slaglengde-måling og avstand-til-punkter-på-banen er lagret i CLAUDE.md og FEATURE_BACKLOG.md, med en tydelig flagget avhengighet: dette krever geografiske/GPS-data som ingen nåværende kilde (verken teeoff eller den nye banekatalogen) har.
2026-07-22 11:31:08 +02:00
|
|
|
|
|
|
|
|
|
|
async def _resolve_teeoff_course(facility_slug: str, teeoff_course_id: int) -> _ResolvedCourse:
|
|
|
|
|
|
try:
|
|
|
|
|
|
facility = await teeoff_client.get_facility(facility_slug)
|
|
|
|
|
|
except teeoff_client.TeeoffNotFoundError:
|
|
|
|
|
|
raise app_error(404, "NOT_FOUND", "Anlegget finnes ikke i teeoff.")
|
|
|
|
|
|
except teeoff_client.TeeoffUnavailableError:
|
|
|
|
|
|
raise app_error(502, "EXTERNAL_SERVICE_UNAVAILABLE", "Klarte ikke å hente baneinfo fra teeoff akkurat nå.")
|
|
|
|
|
|
|
|
|
|
|
|
course_data = next((c for c in facility.get("courses", []) if c.get("id") == teeoff_course_id), None)
|
|
|
|
|
|
if course_data is None:
|
|
|
|
|
|
raise app_error(404, "NOT_FOUND", "Banen finnes ikke på dette anlegget i teeoff.")
|
|
|
|
|
|
|
|
|
|
|
|
raw_holes = course_data.get("holes") or []
|
|
|
|
|
|
if len(raw_holes) != 18:
|
|
|
|
|
|
raise app_error(400, "EXTERNAL_DATA_INCOMPLETE", "Banen har ikke 18 registrerte hull i teeoff ennå.")
|
|
|
|
|
|
for h in raw_holes:
|
|
|
|
|
|
if h.get("par") is None or h.get("hcp_index") is None:
|
|
|
|
|
|
raise app_error(400, "EXTERNAL_DATA_INCOMPLETE", "Banen mangler par eller HCP-index på ett eller flere hull.")
|
|
|
|
|
|
|
2026-07-25 06:29:09 +02:00
|
|
|
|
# Samme "kun ett banenavn -- hopp over duplikat anleggsnavn"-regel som
|
|
|
|
|
|
# `courses.py` sin `import_official_course` (2026-07-25).
|
|
|
|
|
|
if len(facility.get("courses", [])) == 1:
|
|
|
|
|
|
course_name = facility.get("name") or "Bane"
|
|
|
|
|
|
else:
|
|
|
|
|
|
course_name = f"{facility.get('name')} – {course_data.get('name') or 'Bane'}"
|
Migrasjonen er kjørt, og jeg har fortsatt helt til et komplett, verifisert API-lag.
Underveis fant og fikset jeg et reelt hull: round_participant manglet kolonner for selve rating-tallene (kun tee-navnet var lagret) — løst med migrasjon 021, kjørt mot ekte database sammen med resten.
Bygget og rullet ut:
app/routers/rounds.py — opprett/liste/hent/slett runde, legg til/fjern gjestedeltakere, registrer statistikk hull for hull, og en fullfør-endepunkt som kjører hele kjeden (Adjusted Gross Score → Score Differential → tellende-vurdering).
26 scratch-sjekker grønt, pluss en egen test mot den ekte kjørende teeoff-tjenesten som bekreftet at live-oppslaget fungerer nøyaktig som tiltenkt (ingen bane-kopi skrives noe sted).
Begge migrasjoner + ny kode er nå live mot ekte teecup_db/teecup_api. teeoff.no upåvirket.
Jeg la også inn /rounds og /personal-courses i frontendens proxy-oppsett proaktivt, siden prosjektet har blitt bitt av glemte proxy-ruter før — ikke deployet ennå, tas med neste gang frontend faktisk bygges.
Notatet om slaglengde-måling og avstand-til-punkter-på-banen er lagret i CLAUDE.md og FEATURE_BACKLOG.md, med en tydelig flagget avhengighet: dette krever geografiske/GPS-data som ingen nåværende kilde (verken teeoff eller den nye banekatalogen) har.
2026-07-22 11:31:08 +02:00
|
|
|
|
resolved = _ResolvedCourse(
|
|
|
|
|
|
holes=sorted((h["hole_number"], h["par"], h["hcp_index"]) for h in raw_holes),
|
|
|
|
|
|
course_name=course_name,
|
|
|
|
|
|
)
|
|
|
|
|
|
for t in course_data.get("tees") or []:
|
|
|
|
|
|
name = t.get("name") or "Tee"
|
|
|
|
|
|
if t.get("cr_men") is not None and t.get("slope_men") is not None:
|
|
|
|
|
|
resolved.add_rating(name, "m", float(t["cr_men"]), int(t["slope_men"]), sum(h[1] for h in resolved.holes))
|
|
|
|
|
|
if t.get("cr_women") is not None and t.get("slope_women") is not None:
|
|
|
|
|
|
resolved.add_rating(name, "f", float(t["cr_women"]), int(t["slope_women"]), sum(h[1] for h in resolved.holes))
|
|
|
|
|
|
return resolved
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async def _resolve_personal_course(personal_course_id: str) -> _ResolvedCourse:
|
|
|
|
|
|
async with plain_connection() as conn:
|
|
|
|
|
|
course_row = await conn.fetchrow("SELECT name FROM personal_course WHERE id = $1", personal_course_id)
|
|
|
|
|
|
if course_row is None:
|
|
|
|
|
|
raise app_error(404, "NOT_FOUND", "Den egendefinerte banen finnes ikke.")
|
|
|
|
|
|
hole_rows = await conn.fetch(
|
|
|
|
|
|
"SELECT hole_number, par, stroke_index FROM personal_course_hole WHERE personal_course_id = $1",
|
|
|
|
|
|
personal_course_id,
|
|
|
|
|
|
)
|
|
|
|
|
|
tee_rows = await conn.fetch(
|
|
|
|
|
|
"""
|
|
|
|
|
|
SELECT t.name AS tee_name, r.gender, r.course_rating, r.slope_rating, r.par
|
|
|
|
|
|
FROM personal_course_tee t
|
|
|
|
|
|
JOIN personal_course_tee_rating r ON r.personal_course_tee_id = t.id
|
|
|
|
|
|
WHERE t.personal_course_id = $1
|
|
|
|
|
|
""",
|
|
|
|
|
|
personal_course_id,
|
|
|
|
|
|
)
|
|
|
|
|
|
resolved = _ResolvedCourse(
|
|
|
|
|
|
holes=sorted((h["hole_number"], h["par"], h["stroke_index"]) for h in hole_rows),
|
|
|
|
|
|
course_name=course_row["name"],
|
|
|
|
|
|
)
|
|
|
|
|
|
for t in tee_rows:
|
|
|
|
|
|
resolved.add_rating(t["tee_name"], t["gender"], float(t["course_rating"]), t["slope_rating"], t["par"])
|
|
|
|
|
|
return resolved
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
# Runder
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
|
2026-07-24 06:27:35 +02:00
|
|
|
|
# Statistikknivå per deltaker (2026-07-24): kun slag er strengt tatt
|
|
|
|
|
|
# nødvendig for resultat/HCP -- putter og "flere detaljer" er valgfritt,
|
|
|
|
|
|
# av som default. GIR krever putts og vises derfor ikke i strokes_only.
|
|
|
|
|
|
StatLevel = Literal["strokes_only", "strokes_and_putts", "full"]
|
|
|
|
|
|
|
|
|
|
|
|
|
Migrasjonen er kjørt, og jeg har fortsatt helt til et komplett, verifisert API-lag.
Underveis fant og fikset jeg et reelt hull: round_participant manglet kolonner for selve rating-tallene (kun tee-navnet var lagret) — løst med migrasjon 021, kjørt mot ekte database sammen med resten.
Bygget og rullet ut:
app/routers/rounds.py — opprett/liste/hent/slett runde, legg til/fjern gjestedeltakere, registrer statistikk hull for hull, og en fullfør-endepunkt som kjører hele kjeden (Adjusted Gross Score → Score Differential → tellende-vurdering).
26 scratch-sjekker grønt, pluss en egen test mot den ekte kjørende teeoff-tjenesten som bekreftet at live-oppslaget fungerer nøyaktig som tiltenkt (ingen bane-kopi skrives noe sted).
Begge migrasjoner + ny kode er nå live mot ekte teecup_db/teecup_api. teeoff.no upåvirket.
Jeg la også inn /rounds og /personal-courses i frontendens proxy-oppsett proaktivt, siden prosjektet har blitt bitt av glemte proxy-ruter før — ikke deployet ennå, tas med neste gang frontend faktisk bygges.
Notatet om slaglengde-måling og avstand-til-punkter-på-banen er lagret i CLAUDE.md og FEATURE_BACKLOG.md, med en tydelig flagget avhengighet: dette krever geografiske/GPS-data som ingen nåværende kilde (verken teeoff eller den nye banekatalogen) har.
2026-07-22 11:31:08 +02:00
|
|
|
|
class RoundCreate(BaseModel):
|
|
|
|
|
|
course_source: Literal["teeoff", "custom"]
|
|
|
|
|
|
teeoff_facility_slug: str | None = None
|
|
|
|
|
|
teeoff_course_id: int | None = None
|
|
|
|
|
|
personal_course_id: str | None = None
|
|
|
|
|
|
tee_name: str
|
2026-07-25 06:29:09 +02:00
|
|
|
|
# Valgfritt eget navn på runden (2026-07-25) -- mangler det, faller
|
|
|
|
|
|
# visningen tilbake til course_name_snapshot, som alltid er satt.
|
|
|
|
|
|
name: str | None = Field(default=None, max_length=200)
|
Migrasjonen er kjørt, og jeg har fortsatt helt til et komplett, verifisert API-lag.
Underveis fant og fikset jeg et reelt hull: round_participant manglet kolonner for selve rating-tallene (kun tee-navnet var lagret) — løst med migrasjon 021, kjørt mot ekte database sammen med resten.
Bygget og rullet ut:
app/routers/rounds.py — opprett/liste/hent/slett runde, legg til/fjern gjestedeltakere, registrer statistikk hull for hull, og en fullfør-endepunkt som kjører hele kjeden (Adjusted Gross Score → Score Differential → tellende-vurdering).
26 scratch-sjekker grønt, pluss en egen test mot den ekte kjørende teeoff-tjenesten som bekreftet at live-oppslaget fungerer nøyaktig som tiltenkt (ingen bane-kopi skrives noe sted).
Begge migrasjoner + ny kode er nå live mot ekte teecup_db/teecup_api. teeoff.no upåvirket.
Jeg la også inn /rounds og /personal-courses i frontendens proxy-oppsett proaktivt, siden prosjektet har blitt bitt av glemte proxy-ruter før — ikke deployet ennå, tas med neste gang frontend faktisk bygges.
Notatet om slaglengde-måling og avstand-til-punkter-på-banen er lagret i CLAUDE.md og FEATURE_BACKLOG.md, med en tydelig flagget avhengighet: dette krever geografiske/GPS-data som ingen nåværende kilde (verken teeoff eller den nye banekatalogen) har.
2026-07-22 11:31:08 +02:00
|
|
|
|
played_at: date
|
|
|
|
|
|
start_hole: int = Field(default=1, ge=1, le=18)
|
|
|
|
|
|
holes_planned: Literal[9, 18] = 18
|
2026-07-24 06:27:35 +02:00
|
|
|
|
stat_level: StatLevel = "strokes_only"
|
2026-07-24 11:21:39 +02:00
|
|
|
|
# Utslagstidspunkt (2026-07-24) -- valgfritt, brukt til å beregne
|
|
|
|
|
|
# tidsbruk (completed_at - started_at) når runden fullføres.
|
|
|
|
|
|
started_at: datetime | None = None
|
Migrasjonen er kjørt, og jeg har fortsatt helt til et komplett, verifisert API-lag.
Underveis fant og fikset jeg et reelt hull: round_participant manglet kolonner for selve rating-tallene (kun tee-navnet var lagret) — løst med migrasjon 021, kjørt mot ekte database sammen med resten.
Bygget og rullet ut:
app/routers/rounds.py — opprett/liste/hent/slett runde, legg til/fjern gjestedeltakere, registrer statistikk hull for hull, og en fullfør-endepunkt som kjører hele kjeden (Adjusted Gross Score → Score Differential → tellende-vurdering).
26 scratch-sjekker grønt, pluss en egen test mot den ekte kjørende teeoff-tjenesten som bekreftet at live-oppslaget fungerer nøyaktig som tiltenkt (ingen bane-kopi skrives noe sted).
Begge migrasjoner + ny kode er nå live mot ekte teecup_db/teecup_api. teeoff.no upåvirket.
Jeg la også inn /rounds og /personal-courses i frontendens proxy-oppsett proaktivt, siden prosjektet har blitt bitt av glemte proxy-ruter før — ikke deployet ennå, tas med neste gang frontend faktisk bygges.
Notatet om slaglengde-måling og avstand-til-punkter-på-banen er lagret i CLAUDE.md og FEATURE_BACKLOG.md, med en tydelig flagget avhengighet: dette krever geografiske/GPS-data som ingen nåværende kilde (verken teeoff eller den nye banekatalogen) har.
2026-07-22 11:31:08 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class RoundParticipantOut(BaseModel):
|
|
|
|
|
|
id: str
|
|
|
|
|
|
user_id: str | None
|
|
|
|
|
|
guest_name: str | None
|
2026-07-26 09:42:06 +02:00
|
|
|
|
# Valgfritt kontaktfelt, KUN for gjester (2026-07-26) -- ingen betydning
|
|
|
|
|
|
# for en lenket bruker, som allerede har sin egen kontos e-post.
|
|
|
|
|
|
guest_email: str | None
|
2026-07-26 08:22:41 +02:00
|
|
|
|
# Alltid utfylt -- guest_name for gjester, ellers en LEVENDE oppslått
|
|
|
|
|
|
# app_user.display_name for enhver lenket bruker (eier ELLER medspiller,
|
|
|
|
|
|
# ADR-036 fase 3-utvidelsen 2026-07-26) -- ikke en snapshot, samme
|
|
|
|
|
|
# "vis gjeldende navn"-mønster som org-medlemslister/chat-forfatter.
|
|
|
|
|
|
display_name: str
|
Migrasjonen er kjørt, og jeg har fortsatt helt til et komplett, verifisert API-lag.
Underveis fant og fikset jeg et reelt hull: round_participant manglet kolonner for selve rating-tallene (kun tee-navnet var lagret) — løst med migrasjon 021, kjørt mot ekte database sammen med resten.
Bygget og rullet ut:
app/routers/rounds.py — opprett/liste/hent/slett runde, legg til/fjern gjestedeltakere, registrer statistikk hull for hull, og en fullfør-endepunkt som kjører hele kjeden (Adjusted Gross Score → Score Differential → tellende-vurdering).
26 scratch-sjekker grønt, pluss en egen test mot den ekte kjørende teeoff-tjenesten som bekreftet at live-oppslaget fungerer nøyaktig som tiltenkt (ingen bane-kopi skrives noe sted).
Begge migrasjoner + ny kode er nå live mot ekte teecup_db/teecup_api. teeoff.no upåvirket.
Jeg la også inn /rounds og /personal-courses i frontendens proxy-oppsett proaktivt, siden prosjektet har blitt bitt av glemte proxy-ruter før — ikke deployet ennå, tas med neste gang frontend faktisk bygges.
Notatet om slaglengde-måling og avstand-til-punkter-på-banen er lagret i CLAUDE.md og FEATURE_BACKLOG.md, med en tydelig flagget avhengighet: dette krever geografiske/GPS-data som ingen nåværende kilde (verken teeoff eller den nye banekatalogen) har.
2026-07-22 11:31:08 +02:00
|
|
|
|
is_owner: bool
|
|
|
|
|
|
gender: str
|
2026-07-26 09:18:52 +02:00
|
|
|
|
# Per-deltaker utslagssted (2026-07-26) -- kan avvike fra en annen
|
|
|
|
|
|
# deltakers, f.eks. et kjønnsdelt utslag eller et bevisst enklere valg.
|
|
|
|
|
|
# Redigerbar av eieren via PATCH .../participants/{id}, kun for DENNE
|
|
|
|
|
|
# runden (endrer aldri spillerens profil).
|
|
|
|
|
|
tee_name_snapshot: str
|
Migrasjonen er kjørt, og jeg har fortsatt helt til et komplett, verifisert API-lag.
Underveis fant og fikset jeg et reelt hull: round_participant manglet kolonner for selve rating-tallene (kun tee-navnet var lagret) — løst med migrasjon 021, kjørt mot ekte database sammen med resten.
Bygget og rullet ut:
app/routers/rounds.py — opprett/liste/hent/slett runde, legg til/fjern gjestedeltakere, registrer statistikk hull for hull, og en fullfør-endepunkt som kjører hele kjeden (Adjusted Gross Score → Score Differential → tellende-vurdering).
26 scratch-sjekker grønt, pluss en egen test mot den ekte kjørende teeoff-tjenesten som bekreftet at live-oppslaget fungerer nøyaktig som tiltenkt (ingen bane-kopi skrives noe sted).
Begge migrasjoner + ny kode er nå live mot ekte teecup_db/teecup_api. teeoff.no upåvirket.
Jeg la også inn /rounds og /personal-courses i frontendens proxy-oppsett proaktivt, siden prosjektet har blitt bitt av glemte proxy-ruter før — ikke deployet ennå, tas med neste gang frontend faktisk bygges.
Notatet om slaglengde-måling og avstand-til-punkter-på-banen er lagret i CLAUDE.md og FEATURE_BACKLOG.md, med en tydelig flagget avhengighet: dette krever geografiske/GPS-data som ingen nåværende kilde (verken teeoff eller den nye banekatalogen) har.
2026-07-22 11:31:08 +02:00
|
|
|
|
handicap_index_snapshot: float | None
|
|
|
|
|
|
course_handicap_snapshot: int | None
|
|
|
|
|
|
counts_for_handicap: bool
|
|
|
|
|
|
score_differential: float | None
|
2026-07-24 06:27:35 +02:00
|
|
|
|
stat_level: StatLevel
|
Migrasjonen er kjørt, og jeg har fortsatt helt til et komplett, verifisert API-lag.
Underveis fant og fikset jeg et reelt hull: round_participant manglet kolonner for selve rating-tallene (kun tee-navnet var lagret) — løst med migrasjon 021, kjørt mot ekte database sammen med resten.
Bygget og rullet ut:
app/routers/rounds.py — opprett/liste/hent/slett runde, legg til/fjern gjestedeltakere, registrer statistikk hull for hull, og en fullfør-endepunkt som kjører hele kjeden (Adjusted Gross Score → Score Differential → tellende-vurdering).
26 scratch-sjekker grønt, pluss en egen test mot den ekte kjørende teeoff-tjenesten som bekreftet at live-oppslaget fungerer nøyaktig som tiltenkt (ingen bane-kopi skrives noe sted).
Begge migrasjoner + ny kode er nå live mot ekte teecup_db/teecup_api. teeoff.no upåvirket.
Jeg la også inn /rounds og /personal-courses i frontendens proxy-oppsett proaktivt, siden prosjektet har blitt bitt av glemte proxy-ruter før — ikke deployet ennå, tas med neste gang frontend faktisk bygges.
Notatet om slaglengde-måling og avstand-til-punkter-på-banen er lagret i CLAUDE.md og FEATURE_BACKLOG.md, med en tydelig flagget avhengighet: dette krever geografiske/GPS-data som ingen nåværende kilde (verken teeoff eller den nye banekatalogen) har.
2026-07-22 11:31:08 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class RoundOut(BaseModel):
|
|
|
|
|
|
id: str
|
|
|
|
|
|
course_source: str
|
2026-07-25 06:29:09 +02:00
|
|
|
|
name: str | None
|
Migrasjonen er kjørt, og jeg har fortsatt helt til et komplett, verifisert API-lag.
Underveis fant og fikset jeg et reelt hull: round_participant manglet kolonner for selve rating-tallene (kun tee-navnet var lagret) — løst med migrasjon 021, kjørt mot ekte database sammen med resten.
Bygget og rullet ut:
app/routers/rounds.py — opprett/liste/hent/slett runde, legg til/fjern gjestedeltakere, registrer statistikk hull for hull, og en fullfør-endepunkt som kjører hele kjeden (Adjusted Gross Score → Score Differential → tellende-vurdering).
26 scratch-sjekker grønt, pluss en egen test mot den ekte kjørende teeoff-tjenesten som bekreftet at live-oppslaget fungerer nøyaktig som tiltenkt (ingen bane-kopi skrives noe sted).
Begge migrasjoner + ny kode er nå live mot ekte teecup_db/teecup_api. teeoff.no upåvirket.
Jeg la også inn /rounds og /personal-courses i frontendens proxy-oppsett proaktivt, siden prosjektet har blitt bitt av glemte proxy-ruter før — ikke deployet ennå, tas med neste gang frontend faktisk bygges.
Notatet om slaglengde-måling og avstand-til-punkter-på-banen er lagret i CLAUDE.md og FEATURE_BACKLOG.md, med en tydelig flagget avhengighet: dette krever geografiske/GPS-data som ingen nåværende kilde (verken teeoff eller den nye banekatalogen) har.
2026-07-22 11:31:08 +02:00
|
|
|
|
course_name_snapshot: str
|
|
|
|
|
|
tee_name_snapshot: str
|
|
|
|
|
|
played_at: date
|
|
|
|
|
|
start_hole: int
|
|
|
|
|
|
holes_planned: int
|
2026-07-24 11:21:39 +02:00
|
|
|
|
started_at: str | None
|
Migrasjonen er kjørt, og jeg har fortsatt helt til et komplett, verifisert API-lag.
Underveis fant og fikset jeg et reelt hull: round_participant manglet kolonner for selve rating-tallene (kun tee-navnet var lagret) — løst med migrasjon 021, kjørt mot ekte database sammen med resten.
Bygget og rullet ut:
app/routers/rounds.py — opprett/liste/hent/slett runde, legg til/fjern gjestedeltakere, registrer statistikk hull for hull, og en fullfør-endepunkt som kjører hele kjeden (Adjusted Gross Score → Score Differential → tellende-vurdering).
26 scratch-sjekker grønt, pluss en egen test mot den ekte kjørende teeoff-tjenesten som bekreftet at live-oppslaget fungerer nøyaktig som tiltenkt (ingen bane-kopi skrives noe sted).
Begge migrasjoner + ny kode er nå live mot ekte teecup_db/teecup_api. teeoff.no upåvirket.
Jeg la også inn /rounds og /personal-courses i frontendens proxy-oppsett proaktivt, siden prosjektet har blitt bitt av glemte proxy-ruter før — ikke deployet ennå, tas med neste gang frontend faktisk bygges.
Notatet om slaglengde-måling og avstand-til-punkter-på-banen er lagret i CLAUDE.md og FEATURE_BACKLOG.md, med en tydelig flagget avhengighet: dette krever geografiske/GPS-data som ingen nåværende kilde (verken teeoff eller den nye banekatalogen) har.
2026-07-22 11:31:08 +02:00
|
|
|
|
completed_at: str | None
|
|
|
|
|
|
participants: list[RoundParticipantOut]
|
2026-07-26 08:22:41 +02:00
|
|
|
|
# Den SPØRRENDE brukerens (eier ELLER en lenket medspiller, ADR-036
|
|
|
|
|
|
# fase 3-utvidelsen 2026-07-26) egen fremdrift/score, utledet fra
|
|
|
|
|
|
# round_hole (aldri lagret) -- omdøpt fra `owner_*` til `my_*` samme
|
|
|
|
|
|
# dag, siden feltet nå er VIEWER-relativt, ikke alltid eierens.
|
|
|
|
|
|
# `my_score_to_par` er None helt til minst ett hull er registrert.
|
|
|
|
|
|
my_holes_played: int
|
|
|
|
|
|
my_total_score: int | None
|
|
|
|
|
|
my_score_to_par: int | None
|
Migrasjonen er kjørt, og jeg har fortsatt helt til et komplett, verifisert API-lag.
Underveis fant og fikset jeg et reelt hull: round_participant manglet kolonner for selve rating-tallene (kun tee-navnet var lagret) — løst med migrasjon 021, kjørt mot ekte database sammen med resten.
Bygget og rullet ut:
app/routers/rounds.py — opprett/liste/hent/slett runde, legg til/fjern gjestedeltakere, registrer statistikk hull for hull, og en fullfør-endepunkt som kjører hele kjeden (Adjusted Gross Score → Score Differential → tellende-vurdering).
26 scratch-sjekker grønt, pluss en egen test mot den ekte kjørende teeoff-tjenesten som bekreftet at live-oppslaget fungerer nøyaktig som tiltenkt (ingen bane-kopi skrives noe sted).
Begge migrasjoner + ny kode er nå live mot ekte teecup_db/teecup_api. teeoff.no upåvirket.
Jeg la også inn /rounds og /personal-courses i frontendens proxy-oppsett proaktivt, siden prosjektet har blitt bitt av glemte proxy-ruter før — ikke deployet ennå, tas med neste gang frontend faktisk bygges.
Notatet om slaglengde-måling og avstand-til-punkter-på-banen er lagret i CLAUDE.md og FEATURE_BACKLOG.md, med en tydelig flagget avhengighet: dette krever geografiske/GPS-data som ingen nåværende kilde (verken teeoff eller den nye banekatalogen) har.
2026-07-22 11:31:08 +02:00
|
|
|
|
|
|
|
|
|
|
|
2026-07-26 08:22:41 +02:00
|
|
|
|
async def _load_round_out(conn, round_id: str, viewer_user_id: str) -> RoundOut:
|
Migrasjonen er kjørt, og jeg har fortsatt helt til et komplett, verifisert API-lag.
Underveis fant og fikset jeg et reelt hull: round_participant manglet kolonner for selve rating-tallene (kun tee-navnet var lagret) — løst med migrasjon 021, kjørt mot ekte database sammen med resten.
Bygget og rullet ut:
app/routers/rounds.py — opprett/liste/hent/slett runde, legg til/fjern gjestedeltakere, registrer statistikk hull for hull, og en fullfør-endepunkt som kjører hele kjeden (Adjusted Gross Score → Score Differential → tellende-vurdering).
26 scratch-sjekker grønt, pluss en egen test mot den ekte kjørende teeoff-tjenesten som bekreftet at live-oppslaget fungerer nøyaktig som tiltenkt (ingen bane-kopi skrives noe sted).
Begge migrasjoner + ny kode er nå live mot ekte teecup_db/teecup_api. teeoff.no upåvirket.
Jeg la også inn /rounds og /personal-courses i frontendens proxy-oppsett proaktivt, siden prosjektet har blitt bitt av glemte proxy-ruter før — ikke deployet ennå, tas med neste gang frontend faktisk bygges.
Notatet om slaglengde-måling og avstand-til-punkter-på-banen er lagret i CLAUDE.md og FEATURE_BACKLOG.md, med en tydelig flagget avhengighet: dette krever geografiske/GPS-data som ingen nåværende kilde (verken teeoff eller den nye banekatalogen) har.
2026-07-22 11:31:08 +02:00
|
|
|
|
round_row = await conn.fetchrow(
|
|
|
|
|
|
"""
|
2026-07-25 06:29:09 +02:00
|
|
|
|
SELECT id::text AS id, course_source, name, course_name_snapshot, tee_name_snapshot,
|
2026-07-24 11:21:39 +02:00
|
|
|
|
played_at, start_hole, holes_planned, started_at, completed_at
|
Migrasjonen er kjørt, og jeg har fortsatt helt til et komplett, verifisert API-lag.
Underveis fant og fikset jeg et reelt hull: round_participant manglet kolonner for selve rating-tallene (kun tee-navnet var lagret) — løst med migrasjon 021, kjørt mot ekte database sammen med resten.
Bygget og rullet ut:
app/routers/rounds.py — opprett/liste/hent/slett runde, legg til/fjern gjestedeltakere, registrer statistikk hull for hull, og en fullfør-endepunkt som kjører hele kjeden (Adjusted Gross Score → Score Differential → tellende-vurdering).
26 scratch-sjekker grønt, pluss en egen test mot den ekte kjørende teeoff-tjenesten som bekreftet at live-oppslaget fungerer nøyaktig som tiltenkt (ingen bane-kopi skrives noe sted).
Begge migrasjoner + ny kode er nå live mot ekte teecup_db/teecup_api. teeoff.no upåvirket.
Jeg la også inn /rounds og /personal-courses i frontendens proxy-oppsett proaktivt, siden prosjektet har blitt bitt av glemte proxy-ruter før — ikke deployet ennå, tas med neste gang frontend faktisk bygges.
Notatet om slaglengde-måling og avstand-til-punkter-på-banen er lagret i CLAUDE.md og FEATURE_BACKLOG.md, med en tydelig flagget avhengighet: dette krever geografiske/GPS-data som ingen nåværende kilde (verken teeoff eller den nye banekatalogen) har.
2026-07-22 11:31:08 +02:00
|
|
|
|
FROM round WHERE id = $1
|
|
|
|
|
|
""",
|
|
|
|
|
|
round_id,
|
|
|
|
|
|
)
|
|
|
|
|
|
participant_rows = await conn.fetch(
|
|
|
|
|
|
"""
|
2026-07-26 09:42:06 +02:00
|
|
|
|
SELECT rp.id::text AS id, rp.user_id::text AS user_id, rp.guest_name, rp.guest_email,
|
2026-07-26 08:22:41 +02:00
|
|
|
|
COALESCE(rp.guest_name, au.display_name, 'Medspiller') AS display_name,
|
2026-07-26 09:18:52 +02:00
|
|
|
|
rp.is_owner, rp.gender, rp.tee_name_snapshot,
|
2026-07-26 08:22:41 +02:00
|
|
|
|
rp.handicap_index_snapshot::float AS handicap_index_snapshot,
|
|
|
|
|
|
rp.course_handicap_snapshot, rp.counts_for_handicap,
|
|
|
|
|
|
rp.score_differential::float AS score_differential, rp.stat_level
|
|
|
|
|
|
FROM round_participant rp
|
|
|
|
|
|
LEFT JOIN app_user au ON au.id = rp.user_id
|
|
|
|
|
|
WHERE rp.round_id = $1 ORDER BY rp.is_owner DESC, rp.created_at
|
Migrasjonen er kjørt, og jeg har fortsatt helt til et komplett, verifisert API-lag.
Underveis fant og fikset jeg et reelt hull: round_participant manglet kolonner for selve rating-tallene (kun tee-navnet var lagret) — løst med migrasjon 021, kjørt mot ekte database sammen med resten.
Bygget og rullet ut:
app/routers/rounds.py — opprett/liste/hent/slett runde, legg til/fjern gjestedeltakere, registrer statistikk hull for hull, og en fullfør-endepunkt som kjører hele kjeden (Adjusted Gross Score → Score Differential → tellende-vurdering).
26 scratch-sjekker grønt, pluss en egen test mot den ekte kjørende teeoff-tjenesten som bekreftet at live-oppslaget fungerer nøyaktig som tiltenkt (ingen bane-kopi skrives noe sted).
Begge migrasjoner + ny kode er nå live mot ekte teecup_db/teecup_api. teeoff.no upåvirket.
Jeg la også inn /rounds og /personal-courses i frontendens proxy-oppsett proaktivt, siden prosjektet har blitt bitt av glemte proxy-ruter før — ikke deployet ennå, tas med neste gang frontend faktisk bygges.
Notatet om slaglengde-måling og avstand-til-punkter-på-banen er lagret i CLAUDE.md og FEATURE_BACKLOG.md, med en tydelig flagget avhengighet: dette krever geografiske/GPS-data som ingen nåværende kilde (verken teeoff eller den nye banekatalogen) har.
2026-07-22 11:31:08 +02:00
|
|
|
|
""",
|
|
|
|
|
|
round_id,
|
|
|
|
|
|
)
|
2026-07-26 08:22:41 +02:00
|
|
|
|
# Viewer-relativt (ADR-036 fase 3-utvidelsen 2026-07-26): en lenket
|
|
|
|
|
|
# medspiller som ser SIN EGEN "Egne runder"-liste skal se SIN EGEN
|
|
|
|
|
|
# fremdrift her, ikke alltid eierens. Faller tilbake til eierens rad
|
|
|
|
|
|
# i det usannsynlige tilfellet spørreren ikke har noen egen
|
|
|
|
|
|
# deltaker-rad (bør ikke skje, siden tilgang allerede er gatet til
|
|
|
|
|
|
# eier-eller-deltaker av kallerne).
|
|
|
|
|
|
viewer_participant_id = next((r["id"] for r in participant_rows if r["user_id"] == viewer_user_id), None)
|
|
|
|
|
|
target_id = viewer_participant_id or next((r["id"] for r in participant_rows if r["is_owner"]), None)
|
|
|
|
|
|
viewer_agg = await conn.fetchrow(
|
Nytt horisontalt scorekort (/my-rounds/[id]/scorecard) — ekte HTML-tabell med faste kolonnebredder, ingen scroll i det hele tatt. Score-cellene bruker form (sirkel = under par, firkant = over par) + fylt/ufylt i stedet for farge alene, med egen symbolforklaring. Ut/Inn-blokker for 18-hulls runder, én blokk for 9-hulls. Egen dedikert side (ikke sammenslått med statistikk-siden), lenket fra både "Runde fullført"-banneret og fra statistikk-siden. Byttet ut V0s egen forenklede HCP-formel med backends allerede beregnede strokes_received, så det ikke finnes to ulike slagfordelings-implementasjoner i appen. Den gamle vertikale scorekort-tabellen i statistikk-siden er fjernet — nå bor det rå scorekortet kun ett sted.
Anywayslag-fiksen din: lagt til som en tredje flis ved siden av Bunkerslag/Straffeslag, og seksjonen omdøpt fra "Chip, bunker og straffeslag" til "Annet" — notert i .md-filene at et notatfelt trolig kommer dit senere.
Ekte typesjekket build, ingen backend-endring nødvendig, teeoff.no upåvirket.
2026-07-24 23:33:41 +02:00
|
|
|
|
"""
|
|
|
|
|
|
SELECT COUNT(*) FILTER (WHERE played) AS played_count,
|
|
|
|
|
|
COALESCE(SUM(score) FILTER (WHERE played), 0) AS total_score,
|
|
|
|
|
|
COALESCE(SUM(par) FILTER (WHERE played), 0) AS total_par
|
|
|
|
|
|
FROM round_hole WHERE round_participant_id = $1
|
|
|
|
|
|
""",
|
2026-07-26 08:22:41 +02:00
|
|
|
|
target_id,
|
Nytt horisontalt scorekort (/my-rounds/[id]/scorecard) — ekte HTML-tabell med faste kolonnebredder, ingen scroll i det hele tatt. Score-cellene bruker form (sirkel = under par, firkant = over par) + fylt/ufylt i stedet for farge alene, med egen symbolforklaring. Ut/Inn-blokker for 18-hulls runder, én blokk for 9-hulls. Egen dedikert side (ikke sammenslått med statistikk-siden), lenket fra både "Runde fullført"-banneret og fra statistikk-siden. Byttet ut V0s egen forenklede HCP-formel med backends allerede beregnede strokes_received, så det ikke finnes to ulike slagfordelings-implementasjoner i appen. Den gamle vertikale scorekort-tabellen i statistikk-siden er fjernet — nå bor det rå scorekortet kun ett sted.
Anywayslag-fiksen din: lagt til som en tredje flis ved siden av Bunkerslag/Straffeslag, og seksjonen omdøpt fra "Chip, bunker og straffeslag" til "Annet" — notert i .md-filene at et notatfelt trolig kommer dit senere.
Ekte typesjekket build, ingen backend-endring nødvendig, teeoff.no upåvirket.
2026-07-24 23:33:41 +02:00
|
|
|
|
)
|
2026-07-26 08:22:41 +02:00
|
|
|
|
my_holes_played = viewer_agg["played_count"] if viewer_agg else 0
|
|
|
|
|
|
my_total_score = viewer_agg["total_score"] if my_holes_played > 0 else None
|
|
|
|
|
|
my_score_to_par = (
|
|
|
|
|
|
viewer_agg["total_score"] - viewer_agg["total_par"] if my_holes_played > 0 else None
|
Nytt horisontalt scorekort (/my-rounds/[id]/scorecard) — ekte HTML-tabell med faste kolonnebredder, ingen scroll i det hele tatt. Score-cellene bruker form (sirkel = under par, firkant = over par) + fylt/ufylt i stedet for farge alene, med egen symbolforklaring. Ut/Inn-blokker for 18-hulls runder, én blokk for 9-hulls. Egen dedikert side (ikke sammenslått med statistikk-siden), lenket fra både "Runde fullført"-banneret og fra statistikk-siden. Byttet ut V0s egen forenklede HCP-formel med backends allerede beregnede strokes_received, så det ikke finnes to ulike slagfordelings-implementasjoner i appen. Den gamle vertikale scorekort-tabellen i statistikk-siden er fjernet — nå bor det rå scorekortet kun ett sted.
Anywayslag-fiksen din: lagt til som en tredje flis ved siden av Bunkerslag/Straffeslag, og seksjonen omdøpt fra "Chip, bunker og straffeslag" til "Annet" — notert i .md-filene at et notatfelt trolig kommer dit senere.
Ekte typesjekket build, ingen backend-endring nødvendig, teeoff.no upåvirket.
2026-07-24 23:33:41 +02:00
|
|
|
|
)
|
Migrasjonen er kjørt, og jeg har fortsatt helt til et komplett, verifisert API-lag.
Underveis fant og fikset jeg et reelt hull: round_participant manglet kolonner for selve rating-tallene (kun tee-navnet var lagret) — løst med migrasjon 021, kjørt mot ekte database sammen med resten.
Bygget og rullet ut:
app/routers/rounds.py — opprett/liste/hent/slett runde, legg til/fjern gjestedeltakere, registrer statistikk hull for hull, og en fullfør-endepunkt som kjører hele kjeden (Adjusted Gross Score → Score Differential → tellende-vurdering).
26 scratch-sjekker grønt, pluss en egen test mot den ekte kjørende teeoff-tjenesten som bekreftet at live-oppslaget fungerer nøyaktig som tiltenkt (ingen bane-kopi skrives noe sted).
Begge migrasjoner + ny kode er nå live mot ekte teecup_db/teecup_api. teeoff.no upåvirket.
Jeg la også inn /rounds og /personal-courses i frontendens proxy-oppsett proaktivt, siden prosjektet har blitt bitt av glemte proxy-ruter før — ikke deployet ennå, tas med neste gang frontend faktisk bygges.
Notatet om slaglengde-måling og avstand-til-punkter-på-banen er lagret i CLAUDE.md og FEATURE_BACKLOG.md, med en tydelig flagget avhengighet: dette krever geografiske/GPS-data som ingen nåværende kilde (verken teeoff eller den nye banekatalogen) har.
2026-07-22 11:31:08 +02:00
|
|
|
|
return RoundOut(
|
|
|
|
|
|
id=round_row["id"],
|
|
|
|
|
|
course_source=round_row["course_source"],
|
2026-07-25 06:29:09 +02:00
|
|
|
|
name=round_row["name"],
|
Migrasjonen er kjørt, og jeg har fortsatt helt til et komplett, verifisert API-lag.
Underveis fant og fikset jeg et reelt hull: round_participant manglet kolonner for selve rating-tallene (kun tee-navnet var lagret) — løst med migrasjon 021, kjørt mot ekte database sammen med resten.
Bygget og rullet ut:
app/routers/rounds.py — opprett/liste/hent/slett runde, legg til/fjern gjestedeltakere, registrer statistikk hull for hull, og en fullfør-endepunkt som kjører hele kjeden (Adjusted Gross Score → Score Differential → tellende-vurdering).
26 scratch-sjekker grønt, pluss en egen test mot den ekte kjørende teeoff-tjenesten som bekreftet at live-oppslaget fungerer nøyaktig som tiltenkt (ingen bane-kopi skrives noe sted).
Begge migrasjoner + ny kode er nå live mot ekte teecup_db/teecup_api. teeoff.no upåvirket.
Jeg la også inn /rounds og /personal-courses i frontendens proxy-oppsett proaktivt, siden prosjektet har blitt bitt av glemte proxy-ruter før — ikke deployet ennå, tas med neste gang frontend faktisk bygges.
Notatet om slaglengde-måling og avstand-til-punkter-på-banen er lagret i CLAUDE.md og FEATURE_BACKLOG.md, med en tydelig flagget avhengighet: dette krever geografiske/GPS-data som ingen nåværende kilde (verken teeoff eller den nye banekatalogen) har.
2026-07-22 11:31:08 +02:00
|
|
|
|
course_name_snapshot=round_row["course_name_snapshot"],
|
|
|
|
|
|
tee_name_snapshot=round_row["tee_name_snapshot"],
|
|
|
|
|
|
played_at=round_row["played_at"],
|
|
|
|
|
|
start_hole=round_row["start_hole"],
|
|
|
|
|
|
holes_planned=round_row["holes_planned"],
|
2026-07-24 11:21:39 +02:00
|
|
|
|
started_at=round_row["started_at"].isoformat() if round_row["started_at"] else None,
|
Migrasjonen er kjørt, og jeg har fortsatt helt til et komplett, verifisert API-lag.
Underveis fant og fikset jeg et reelt hull: round_participant manglet kolonner for selve rating-tallene (kun tee-navnet var lagret) — løst med migrasjon 021, kjørt mot ekte database sammen med resten.
Bygget og rullet ut:
app/routers/rounds.py — opprett/liste/hent/slett runde, legg til/fjern gjestedeltakere, registrer statistikk hull for hull, og en fullfør-endepunkt som kjører hele kjeden (Adjusted Gross Score → Score Differential → tellende-vurdering).
26 scratch-sjekker grønt, pluss en egen test mot den ekte kjørende teeoff-tjenesten som bekreftet at live-oppslaget fungerer nøyaktig som tiltenkt (ingen bane-kopi skrives noe sted).
Begge migrasjoner + ny kode er nå live mot ekte teecup_db/teecup_api. teeoff.no upåvirket.
Jeg la også inn /rounds og /personal-courses i frontendens proxy-oppsett proaktivt, siden prosjektet har blitt bitt av glemte proxy-ruter før — ikke deployet ennå, tas med neste gang frontend faktisk bygges.
Notatet om slaglengde-måling og avstand-til-punkter-på-banen er lagret i CLAUDE.md og FEATURE_BACKLOG.md, med en tydelig flagget avhengighet: dette krever geografiske/GPS-data som ingen nåværende kilde (verken teeoff eller den nye banekatalogen) har.
2026-07-22 11:31:08 +02:00
|
|
|
|
completed_at=round_row["completed_at"].isoformat() if round_row["completed_at"] else None,
|
|
|
|
|
|
participants=[RoundParticipantOut(**dict(r)) for r in participant_rows],
|
2026-07-26 08:22:41 +02:00
|
|
|
|
my_holes_played=my_holes_played,
|
|
|
|
|
|
my_total_score=my_total_score,
|
|
|
|
|
|
my_score_to_par=my_score_to_par,
|
Migrasjonen er kjørt, og jeg har fortsatt helt til et komplett, verifisert API-lag.
Underveis fant og fikset jeg et reelt hull: round_participant manglet kolonner for selve rating-tallene (kun tee-navnet var lagret) — løst med migrasjon 021, kjørt mot ekte database sammen med resten.
Bygget og rullet ut:
app/routers/rounds.py — opprett/liste/hent/slett runde, legg til/fjern gjestedeltakere, registrer statistikk hull for hull, og en fullfør-endepunkt som kjører hele kjeden (Adjusted Gross Score → Score Differential → tellende-vurdering).
26 scratch-sjekker grønt, pluss en egen test mot den ekte kjørende teeoff-tjenesten som bekreftet at live-oppslaget fungerer nøyaktig som tiltenkt (ingen bane-kopi skrives noe sted).
Begge migrasjoner + ny kode er nå live mot ekte teecup_db/teecup_api. teeoff.no upåvirket.
Jeg la også inn /rounds og /personal-courses i frontendens proxy-oppsett proaktivt, siden prosjektet har blitt bitt av glemte proxy-ruter før — ikke deployet ennå, tas med neste gang frontend faktisk bygges.
Notatet om slaglengde-måling og avstand-til-punkter-på-banen er lagret i CLAUDE.md og FEATURE_BACKLOG.md, med en tydelig flagget avhengighet: dette krever geografiske/GPS-data som ingen nåværende kilde (verken teeoff eller den nye banekatalogen) har.
2026-07-22 11:31:08 +02:00
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async def _create_participant(
|
|
|
|
|
|
conn,
|
|
|
|
|
|
round_id: str,
|
|
|
|
|
|
resolved: _ResolvedCourse,
|
|
|
|
|
|
tee_name: str,
|
|
|
|
|
|
*,
|
|
|
|
|
|
user_id: str | None,
|
|
|
|
|
|
guest_name: str | None,
|
|
|
|
|
|
is_owner: bool,
|
|
|
|
|
|
gender: str,
|
|
|
|
|
|
handicap_index: float | None,
|
2026-07-24 06:27:35 +02:00
|
|
|
|
stat_level: StatLevel = "strokes_only",
|
2026-07-26 09:42:06 +02:00
|
|
|
|
guest_email: str | None = None,
|
Migrasjonen er kjørt, og jeg har fortsatt helt til et komplett, verifisert API-lag.
Underveis fant og fikset jeg et reelt hull: round_participant manglet kolonner for selve rating-tallene (kun tee-navnet var lagret) — løst med migrasjon 021, kjørt mot ekte database sammen med resten.
Bygget og rullet ut:
app/routers/rounds.py — opprett/liste/hent/slett runde, legg til/fjern gjestedeltakere, registrer statistikk hull for hull, og en fullfør-endepunkt som kjører hele kjeden (Adjusted Gross Score → Score Differential → tellende-vurdering).
26 scratch-sjekker grønt, pluss en egen test mot den ekte kjørende teeoff-tjenesten som bekreftet at live-oppslaget fungerer nøyaktig som tiltenkt (ingen bane-kopi skrives noe sted).
Begge migrasjoner + ny kode er nå live mot ekte teecup_db/teecup_api. teeoff.no upåvirket.
Jeg la også inn /rounds og /personal-courses i frontendens proxy-oppsett proaktivt, siden prosjektet har blitt bitt av glemte proxy-ruter før — ikke deployet ennå, tas med neste gang frontend faktisk bygges.
Notatet om slaglengde-måling og avstand-til-punkter-på-banen er lagret i CLAUDE.md og FEATURE_BACKLOG.md, med en tydelig flagget avhengighet: dette krever geografiske/GPS-data som ingen nåværende kilde (verken teeoff eller den nye banekatalogen) har.
2026-07-22 11:31:08 +02:00
|
|
|
|
) -> str:
|
|
|
|
|
|
rating = resolved.rating_for(tee_name, gender)
|
|
|
|
|
|
course_rating = slope_rating = tee_par = None
|
|
|
|
|
|
course_handicap_snapshot = None
|
|
|
|
|
|
if rating is not None:
|
|
|
|
|
|
course_rating, slope_rating, tee_par = rating
|
|
|
|
|
|
if handicap_index is not None:
|
|
|
|
|
|
course_handicap_snapshot = course_handicap(handicap_index, slope_rating, course_rating, tee_par)
|
|
|
|
|
|
|
|
|
|
|
|
participant_row = await conn.fetchrow(
|
|
|
|
|
|
"""
|
|
|
|
|
|
INSERT INTO round_participant
|
2026-07-26 09:42:06 +02:00
|
|
|
|
(round_id, user_id, guest_name, guest_email, is_owner, gender, tee_name_snapshot, handicap_index_snapshot,
|
2026-07-24 06:27:35 +02:00
|
|
|
|
course_rating_snapshot, slope_rating_snapshot, tee_par_snapshot, course_handicap_snapshot,
|
|
|
|
|
|
stat_level)
|
2026-07-26 09:42:06 +02:00
|
|
|
|
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13)
|
Migrasjonen er kjørt, og jeg har fortsatt helt til et komplett, verifisert API-lag.
Underveis fant og fikset jeg et reelt hull: round_participant manglet kolonner for selve rating-tallene (kun tee-navnet var lagret) — løst med migrasjon 021, kjørt mot ekte database sammen med resten.
Bygget og rullet ut:
app/routers/rounds.py — opprett/liste/hent/slett runde, legg til/fjern gjestedeltakere, registrer statistikk hull for hull, og en fullfør-endepunkt som kjører hele kjeden (Adjusted Gross Score → Score Differential → tellende-vurdering).
26 scratch-sjekker grønt, pluss en egen test mot den ekte kjørende teeoff-tjenesten som bekreftet at live-oppslaget fungerer nøyaktig som tiltenkt (ingen bane-kopi skrives noe sted).
Begge migrasjoner + ny kode er nå live mot ekte teecup_db/teecup_api. teeoff.no upåvirket.
Jeg la også inn /rounds og /personal-courses i frontendens proxy-oppsett proaktivt, siden prosjektet har blitt bitt av glemte proxy-ruter før — ikke deployet ennå, tas med neste gang frontend faktisk bygges.
Notatet om slaglengde-måling og avstand-til-punkter-på-banen er lagret i CLAUDE.md og FEATURE_BACKLOG.md, med en tydelig flagget avhengighet: dette krever geografiske/GPS-data som ingen nåværende kilde (verken teeoff eller den nye banekatalogen) har.
2026-07-22 11:31:08 +02:00
|
|
|
|
RETURNING id::text AS id
|
|
|
|
|
|
""",
|
|
|
|
|
|
round_id,
|
|
|
|
|
|
user_id,
|
|
|
|
|
|
guest_name,
|
2026-07-26 09:42:06 +02:00
|
|
|
|
guest_email,
|
Migrasjonen er kjørt, og jeg har fortsatt helt til et komplett, verifisert API-lag.
Underveis fant og fikset jeg et reelt hull: round_participant manglet kolonner for selve rating-tallene (kun tee-navnet var lagret) — løst med migrasjon 021, kjørt mot ekte database sammen med resten.
Bygget og rullet ut:
app/routers/rounds.py — opprett/liste/hent/slett runde, legg til/fjern gjestedeltakere, registrer statistikk hull for hull, og en fullfør-endepunkt som kjører hele kjeden (Adjusted Gross Score → Score Differential → tellende-vurdering).
26 scratch-sjekker grønt, pluss en egen test mot den ekte kjørende teeoff-tjenesten som bekreftet at live-oppslaget fungerer nøyaktig som tiltenkt (ingen bane-kopi skrives noe sted).
Begge migrasjoner + ny kode er nå live mot ekte teecup_db/teecup_api. teeoff.no upåvirket.
Jeg la også inn /rounds og /personal-courses i frontendens proxy-oppsett proaktivt, siden prosjektet har blitt bitt av glemte proxy-ruter før — ikke deployet ennå, tas med neste gang frontend faktisk bygges.
Notatet om slaglengde-måling og avstand-til-punkter-på-banen er lagret i CLAUDE.md og FEATURE_BACKLOG.md, med en tydelig flagget avhengighet: dette krever geografiske/GPS-data som ingen nåværende kilde (verken teeoff eller den nye banekatalogen) har.
2026-07-22 11:31:08 +02:00
|
|
|
|
is_owner,
|
|
|
|
|
|
gender,
|
2026-07-26 09:18:52 +02:00
|
|
|
|
tee_name,
|
Migrasjonen er kjørt, og jeg har fortsatt helt til et komplett, verifisert API-lag.
Underveis fant og fikset jeg et reelt hull: round_participant manglet kolonner for selve rating-tallene (kun tee-navnet var lagret) — løst med migrasjon 021, kjørt mot ekte database sammen med resten.
Bygget og rullet ut:
app/routers/rounds.py — opprett/liste/hent/slett runde, legg til/fjern gjestedeltakere, registrer statistikk hull for hull, og en fullfør-endepunkt som kjører hele kjeden (Adjusted Gross Score → Score Differential → tellende-vurdering).
26 scratch-sjekker grønt, pluss en egen test mot den ekte kjørende teeoff-tjenesten som bekreftet at live-oppslaget fungerer nøyaktig som tiltenkt (ingen bane-kopi skrives noe sted).
Begge migrasjoner + ny kode er nå live mot ekte teecup_db/teecup_api. teeoff.no upåvirket.
Jeg la også inn /rounds og /personal-courses i frontendens proxy-oppsett proaktivt, siden prosjektet har blitt bitt av glemte proxy-ruter før — ikke deployet ennå, tas med neste gang frontend faktisk bygges.
Notatet om slaglengde-måling og avstand-til-punkter-på-banen er lagret i CLAUDE.md og FEATURE_BACKLOG.md, med en tydelig flagget avhengighet: dette krever geografiske/GPS-data som ingen nåværende kilde (verken teeoff eller den nye banekatalogen) har.
2026-07-22 11:31:08 +02:00
|
|
|
|
handicap_index,
|
|
|
|
|
|
course_rating,
|
|
|
|
|
|
slope_rating,
|
|
|
|
|
|
tee_par,
|
|
|
|
|
|
course_handicap_snapshot,
|
2026-07-24 06:27:35 +02:00
|
|
|
|
stat_level,
|
Migrasjonen er kjørt, og jeg har fortsatt helt til et komplett, verifisert API-lag.
Underveis fant og fikset jeg et reelt hull: round_participant manglet kolonner for selve rating-tallene (kun tee-navnet var lagret) — løst med migrasjon 021, kjørt mot ekte database sammen med resten.
Bygget og rullet ut:
app/routers/rounds.py — opprett/liste/hent/slett runde, legg til/fjern gjestedeltakere, registrer statistikk hull for hull, og en fullfør-endepunkt som kjører hele kjeden (Adjusted Gross Score → Score Differential → tellende-vurdering).
26 scratch-sjekker grønt, pluss en egen test mot den ekte kjørende teeoff-tjenesten som bekreftet at live-oppslaget fungerer nøyaktig som tiltenkt (ingen bane-kopi skrives noe sted).
Begge migrasjoner + ny kode er nå live mot ekte teecup_db/teecup_api. teeoff.no upåvirket.
Jeg la også inn /rounds og /personal-courses i frontendens proxy-oppsett proaktivt, siden prosjektet har blitt bitt av glemte proxy-ruter før — ikke deployet ennå, tas med neste gang frontend faktisk bygges.
Notatet om slaglengde-måling og avstand-til-punkter-på-banen er lagret i CLAUDE.md og FEATURE_BACKLOG.md, med en tydelig flagget avhengighet: dette krever geografiske/GPS-data som ingen nåværende kilde (verken teeoff eller den nye banekatalogen) har.
2026-07-22 11:31:08 +02:00
|
|
|
|
)
|
|
|
|
|
|
participant_id = participant_row["id"]
|
|
|
|
|
|
|
|
|
|
|
|
for hole_number, par, stroke_index in resolved.holes:
|
|
|
|
|
|
await conn.execute(
|
|
|
|
|
|
"INSERT INTO round_hole (round_participant_id, hole_number, par, stroke_index) VALUES ($1, $2, $3, $4)",
|
|
|
|
|
|
participant_id,
|
|
|
|
|
|
hole_number,
|
|
|
|
|
|
par,
|
|
|
|
|
|
stroke_index,
|
|
|
|
|
|
)
|
|
|
|
|
|
return participant_id
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.post("/rounds", response_model=RoundOut, status_code=201)
|
|
|
|
|
|
async def create_round(
|
|
|
|
|
|
body: RoundCreate,
|
|
|
|
|
|
user: CurrentUser = Depends(get_current_user),
|
|
|
|
|
|
) -> RoundOut:
|
|
|
|
|
|
if body.course_source == "teeoff":
|
|
|
|
|
|
if not body.teeoff_facility_slug or not body.teeoff_course_id:
|
|
|
|
|
|
raise app_error(400, "VALIDATION_FAILED", "teeoff_facility_slug og teeoff_course_id er påkrevd.")
|
|
|
|
|
|
resolved = await _resolve_teeoff_course(body.teeoff_facility_slug, body.teeoff_course_id)
|
|
|
|
|
|
else:
|
|
|
|
|
|
if not body.personal_course_id:
|
|
|
|
|
|
raise app_error(400, "VALIDATION_FAILED", "personal_course_id er påkrevd.")
|
|
|
|
|
|
resolved = await _resolve_personal_course(body.personal_course_id)
|
|
|
|
|
|
|
|
|
|
|
|
async with plain_connection() as conn:
|
|
|
|
|
|
owner_row = await conn.fetchrow(
|
|
|
|
|
|
"SELECT gender, handicap_index::float AS handicap_index FROM app_user WHERE id = $1",
|
|
|
|
|
|
user.user_id,
|
|
|
|
|
|
)
|
|
|
|
|
|
if owner_row is None or owner_row["gender"] is None:
|
|
|
|
|
|
raise app_error(400, "VALIDATION_FAILED", "Fullfør profilen din (kjønn/HCP) før du registrerer en runde.")
|
|
|
|
|
|
|
|
|
|
|
|
if resolved.rating_for(body.tee_name, owner_row["gender"]) is None:
|
|
|
|
|
|
raise app_error(
|
|
|
|
|
|
400, "VALIDATION_FAILED",
|
|
|
|
|
|
"Valgt utslag har ingen rating for ditt registrerte kjønn på denne banen.",
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
async with conn.transaction(), translate_db_errors():
|
|
|
|
|
|
round_row = await conn.fetchrow(
|
|
|
|
|
|
"""
|
|
|
|
|
|
INSERT INTO round
|
|
|
|
|
|
(owner_user_id, course_source, teeoff_facility_slug, teeoff_course_id,
|
|
|
|
|
|
personal_course_id, course_name_snapshot, tee_name_snapshot, played_at,
|
2026-07-25 06:29:09 +02:00
|
|
|
|
start_hole, holes_planned, started_at, name)
|
|
|
|
|
|
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12)
|
Migrasjonen er kjørt, og jeg har fortsatt helt til et komplett, verifisert API-lag.
Underveis fant og fikset jeg et reelt hull: round_participant manglet kolonner for selve rating-tallene (kun tee-navnet var lagret) — løst med migrasjon 021, kjørt mot ekte database sammen med resten.
Bygget og rullet ut:
app/routers/rounds.py — opprett/liste/hent/slett runde, legg til/fjern gjestedeltakere, registrer statistikk hull for hull, og en fullfør-endepunkt som kjører hele kjeden (Adjusted Gross Score → Score Differential → tellende-vurdering).
26 scratch-sjekker grønt, pluss en egen test mot den ekte kjørende teeoff-tjenesten som bekreftet at live-oppslaget fungerer nøyaktig som tiltenkt (ingen bane-kopi skrives noe sted).
Begge migrasjoner + ny kode er nå live mot ekte teecup_db/teecup_api. teeoff.no upåvirket.
Jeg la også inn /rounds og /personal-courses i frontendens proxy-oppsett proaktivt, siden prosjektet har blitt bitt av glemte proxy-ruter før — ikke deployet ennå, tas med neste gang frontend faktisk bygges.
Notatet om slaglengde-måling og avstand-til-punkter-på-banen er lagret i CLAUDE.md og FEATURE_BACKLOG.md, med en tydelig flagget avhengighet: dette krever geografiske/GPS-data som ingen nåværende kilde (verken teeoff eller den nye banekatalogen) har.
2026-07-22 11:31:08 +02:00
|
|
|
|
RETURNING id::text AS id
|
|
|
|
|
|
""",
|
|
|
|
|
|
user.user_id,
|
|
|
|
|
|
body.course_source,
|
|
|
|
|
|
body.teeoff_facility_slug,
|
|
|
|
|
|
str(body.teeoff_course_id) if body.teeoff_course_id is not None else None,
|
|
|
|
|
|
body.personal_course_id,
|
|
|
|
|
|
resolved.course_name,
|
|
|
|
|
|
body.tee_name,
|
|
|
|
|
|
body.played_at,
|
|
|
|
|
|
body.start_hole,
|
|
|
|
|
|
body.holes_planned,
|
2026-07-24 11:21:39 +02:00
|
|
|
|
body.started_at,
|
2026-07-25 06:29:09 +02:00
|
|
|
|
body.name.strip() if body.name and body.name.strip() else None,
|
Migrasjonen er kjørt, og jeg har fortsatt helt til et komplett, verifisert API-lag.
Underveis fant og fikset jeg et reelt hull: round_participant manglet kolonner for selve rating-tallene (kun tee-navnet var lagret) — løst med migrasjon 021, kjørt mot ekte database sammen med resten.
Bygget og rullet ut:
app/routers/rounds.py — opprett/liste/hent/slett runde, legg til/fjern gjestedeltakere, registrer statistikk hull for hull, og en fullfør-endepunkt som kjører hele kjeden (Adjusted Gross Score → Score Differential → tellende-vurdering).
26 scratch-sjekker grønt, pluss en egen test mot den ekte kjørende teeoff-tjenesten som bekreftet at live-oppslaget fungerer nøyaktig som tiltenkt (ingen bane-kopi skrives noe sted).
Begge migrasjoner + ny kode er nå live mot ekte teecup_db/teecup_api. teeoff.no upåvirket.
Jeg la også inn /rounds og /personal-courses i frontendens proxy-oppsett proaktivt, siden prosjektet har blitt bitt av glemte proxy-ruter før — ikke deployet ennå, tas med neste gang frontend faktisk bygges.
Notatet om slaglengde-måling og avstand-til-punkter-på-banen er lagret i CLAUDE.md og FEATURE_BACKLOG.md, med en tydelig flagget avhengighet: dette krever geografiske/GPS-data som ingen nåværende kilde (verken teeoff eller den nye banekatalogen) har.
2026-07-22 11:31:08 +02:00
|
|
|
|
)
|
|
|
|
|
|
round_id = round_row["id"]
|
|
|
|
|
|
|
|
|
|
|
|
await _create_participant(
|
|
|
|
|
|
conn, round_id, resolved, body.tee_name,
|
|
|
|
|
|
user_id=user.user_id, guest_name=None, is_owner=True,
|
|
|
|
|
|
gender=owner_row["gender"], handicap_index=owner_row["handicap_index"],
|
2026-07-24 06:27:35 +02:00
|
|
|
|
stat_level=body.stat_level,
|
Migrasjonen er kjørt, og jeg har fortsatt helt til et komplett, verifisert API-lag.
Underveis fant og fikset jeg et reelt hull: round_participant manglet kolonner for selve rating-tallene (kun tee-navnet var lagret) — løst med migrasjon 021, kjørt mot ekte database sammen med resten.
Bygget og rullet ut:
app/routers/rounds.py — opprett/liste/hent/slett runde, legg til/fjern gjestedeltakere, registrer statistikk hull for hull, og en fullfør-endepunkt som kjører hele kjeden (Adjusted Gross Score → Score Differential → tellende-vurdering).
26 scratch-sjekker grønt, pluss en egen test mot den ekte kjørende teeoff-tjenesten som bekreftet at live-oppslaget fungerer nøyaktig som tiltenkt (ingen bane-kopi skrives noe sted).
Begge migrasjoner + ny kode er nå live mot ekte teecup_db/teecup_api. teeoff.no upåvirket.
Jeg la også inn /rounds og /personal-courses i frontendens proxy-oppsett proaktivt, siden prosjektet har blitt bitt av glemte proxy-ruter før — ikke deployet ennå, tas med neste gang frontend faktisk bygges.
Notatet om slaglengde-måling og avstand-til-punkter-på-banen er lagret i CLAUDE.md og FEATURE_BACKLOG.md, med en tydelig flagget avhengighet: dette krever geografiske/GPS-data som ingen nåværende kilde (verken teeoff eller den nye banekatalogen) har.
2026-07-22 11:31:08 +02:00
|
|
|
|
)
|
|
|
|
|
|
|
2026-07-26 08:22:41 +02:00
|
|
|
|
return await _load_round_out(conn, round_id, user.user_id)
|
Migrasjonen er kjørt, og jeg har fortsatt helt til et komplett, verifisert API-lag.
Underveis fant og fikset jeg et reelt hull: round_participant manglet kolonner for selve rating-tallene (kun tee-navnet var lagret) — løst med migrasjon 021, kjørt mot ekte database sammen med resten.
Bygget og rullet ut:
app/routers/rounds.py — opprett/liste/hent/slett runde, legg til/fjern gjestedeltakere, registrer statistikk hull for hull, og en fullfør-endepunkt som kjører hele kjeden (Adjusted Gross Score → Score Differential → tellende-vurdering).
26 scratch-sjekker grønt, pluss en egen test mot den ekte kjørende teeoff-tjenesten som bekreftet at live-oppslaget fungerer nøyaktig som tiltenkt (ingen bane-kopi skrives noe sted).
Begge migrasjoner + ny kode er nå live mot ekte teecup_db/teecup_api. teeoff.no upåvirket.
Jeg la også inn /rounds og /personal-courses i frontendens proxy-oppsett proaktivt, siden prosjektet har blitt bitt av glemte proxy-ruter før — ikke deployet ennå, tas med neste gang frontend faktisk bygges.
Notatet om slaglengde-måling og avstand-til-punkter-på-banen er lagret i CLAUDE.md og FEATURE_BACKLOG.md, med en tydelig flagget avhengighet: dette krever geografiske/GPS-data som ingen nåværende kilde (verken teeoff eller den nye banekatalogen) har.
2026-07-22 11:31:08 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.get("/rounds", response_model=list[RoundOut])
|
|
|
|
|
|
async def list_rounds(user: CurrentUser = Depends(get_current_user)) -> list[RoundOut]:
|
|
|
|
|
|
async with plain_connection() as conn:
|
2026-07-26 08:22:41 +02:00
|
|
|
|
# ADR-036 fase 3-utvidelsen (2026-07-26): en lenket medspiller ser nå
|
|
|
|
|
|
# runden i SIN EGEN liste også, ikke bare eieren -- bekreftet av
|
|
|
|
|
|
# brukeren som ønsket allerede 2026-07-25.
|
Migrasjonen er kjørt, og jeg har fortsatt helt til et komplett, verifisert API-lag.
Underveis fant og fikset jeg et reelt hull: round_participant manglet kolonner for selve rating-tallene (kun tee-navnet var lagret) — løst med migrasjon 021, kjørt mot ekte database sammen med resten.
Bygget og rullet ut:
app/routers/rounds.py — opprett/liste/hent/slett runde, legg til/fjern gjestedeltakere, registrer statistikk hull for hull, og en fullfør-endepunkt som kjører hele kjeden (Adjusted Gross Score → Score Differential → tellende-vurdering).
26 scratch-sjekker grønt, pluss en egen test mot den ekte kjørende teeoff-tjenesten som bekreftet at live-oppslaget fungerer nøyaktig som tiltenkt (ingen bane-kopi skrives noe sted).
Begge migrasjoner + ny kode er nå live mot ekte teecup_db/teecup_api. teeoff.no upåvirket.
Jeg la også inn /rounds og /personal-courses i frontendens proxy-oppsett proaktivt, siden prosjektet har blitt bitt av glemte proxy-ruter før — ikke deployet ennå, tas med neste gang frontend faktisk bygges.
Notatet om slaglengde-måling og avstand-til-punkter-på-banen er lagret i CLAUDE.md og FEATURE_BACKLOG.md, med en tydelig flagget avhengighet: dette krever geografiske/GPS-data som ingen nåværende kilde (verken teeoff eller den nye banekatalogen) har.
2026-07-22 11:31:08 +02:00
|
|
|
|
ids = await conn.fetch(
|
2026-07-26 08:22:41 +02:00
|
|
|
|
"""
|
|
|
|
|
|
SELECT r.id::text AS id
|
|
|
|
|
|
FROM round r
|
|
|
|
|
|
WHERE r.owner_user_id = $1
|
|
|
|
|
|
OR EXISTS(SELECT 1 FROM round_participant WHERE round_id = r.id AND user_id = $1)
|
|
|
|
|
|
ORDER BY r.played_at DESC, r.created_at DESC
|
|
|
|
|
|
""",
|
Migrasjonen er kjørt, og jeg har fortsatt helt til et komplett, verifisert API-lag.
Underveis fant og fikset jeg et reelt hull: round_participant manglet kolonner for selve rating-tallene (kun tee-navnet var lagret) — løst med migrasjon 021, kjørt mot ekte database sammen med resten.
Bygget og rullet ut:
app/routers/rounds.py — opprett/liste/hent/slett runde, legg til/fjern gjestedeltakere, registrer statistikk hull for hull, og en fullfør-endepunkt som kjører hele kjeden (Adjusted Gross Score → Score Differential → tellende-vurdering).
26 scratch-sjekker grønt, pluss en egen test mot den ekte kjørende teeoff-tjenesten som bekreftet at live-oppslaget fungerer nøyaktig som tiltenkt (ingen bane-kopi skrives noe sted).
Begge migrasjoner + ny kode er nå live mot ekte teecup_db/teecup_api. teeoff.no upåvirket.
Jeg la også inn /rounds og /personal-courses i frontendens proxy-oppsett proaktivt, siden prosjektet har blitt bitt av glemte proxy-ruter før — ikke deployet ennå, tas med neste gang frontend faktisk bygges.
Notatet om slaglengde-måling og avstand-til-punkter-på-banen er lagret i CLAUDE.md og FEATURE_BACKLOG.md, med en tydelig flagget avhengighet: dette krever geografiske/GPS-data som ingen nåværende kilde (verken teeoff eller den nye banekatalogen) har.
2026-07-22 11:31:08 +02:00
|
|
|
|
user.user_id,
|
|
|
|
|
|
)
|
2026-07-26 08:22:41 +02:00
|
|
|
|
return [await _load_round_out(conn, r["id"], user.user_id) for r in ids]
|
Migrasjonen er kjørt, og jeg har fortsatt helt til et komplett, verifisert API-lag.
Underveis fant og fikset jeg et reelt hull: round_participant manglet kolonner for selve rating-tallene (kun tee-navnet var lagret) — løst med migrasjon 021, kjørt mot ekte database sammen med resten.
Bygget og rullet ut:
app/routers/rounds.py — opprett/liste/hent/slett runde, legg til/fjern gjestedeltakere, registrer statistikk hull for hull, og en fullfør-endepunkt som kjører hele kjeden (Adjusted Gross Score → Score Differential → tellende-vurdering).
26 scratch-sjekker grønt, pluss en egen test mot den ekte kjørende teeoff-tjenesten som bekreftet at live-oppslaget fungerer nøyaktig som tiltenkt (ingen bane-kopi skrives noe sted).
Begge migrasjoner + ny kode er nå live mot ekte teecup_db/teecup_api. teeoff.no upåvirket.
Jeg la også inn /rounds og /personal-courses i frontendens proxy-oppsett proaktivt, siden prosjektet har blitt bitt av glemte proxy-ruter før — ikke deployet ennå, tas med neste gang frontend faktisk bygges.
Notatet om slaglengde-måling og avstand-til-punkter-på-banen er lagret i CLAUDE.md og FEATURE_BACKLOG.md, med en tydelig flagget avhengighet: dette krever geografiske/GPS-data som ingen nåværende kilde (verken teeoff eller den nye banekatalogen) har.
2026-07-22 11:31:08 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async def _get_owned_round_or_404(conn, round_id: str, user_id: str):
|
2026-07-26 08:22:41 +02:00
|
|
|
|
"""Strengt eier-only -- for runde-forvaltning (rediger/slett metadata,
|
|
|
|
|
|
legge til/fjerne deltakere). IKKE for lesing/scoreregistrering, se
|
|
|
|
|
|
`_get_accessible_round_or_404`."""
|
Migrasjonen er kjørt, og jeg har fortsatt helt til et komplett, verifisert API-lag.
Underveis fant og fikset jeg et reelt hull: round_participant manglet kolonner for selve rating-tallene (kun tee-navnet var lagret) — løst med migrasjon 021, kjørt mot ekte database sammen med resten.
Bygget og rullet ut:
app/routers/rounds.py — opprett/liste/hent/slett runde, legg til/fjern gjestedeltakere, registrer statistikk hull for hull, og en fullfør-endepunkt som kjører hele kjeden (Adjusted Gross Score → Score Differential → tellende-vurdering).
26 scratch-sjekker grønt, pluss en egen test mot den ekte kjørende teeoff-tjenesten som bekreftet at live-oppslaget fungerer nøyaktig som tiltenkt (ingen bane-kopi skrives noe sted).
Begge migrasjoner + ny kode er nå live mot ekte teecup_db/teecup_api. teeoff.no upåvirket.
Jeg la også inn /rounds og /personal-courses i frontendens proxy-oppsett proaktivt, siden prosjektet har blitt bitt av glemte proxy-ruter før — ikke deployet ennå, tas med neste gang frontend faktisk bygges.
Notatet om slaglengde-måling og avstand-til-punkter-på-banen er lagret i CLAUDE.md og FEATURE_BACKLOG.md, med en tydelig flagget avhengighet: dette krever geografiske/GPS-data som ingen nåværende kilde (verken teeoff eller den nye banekatalogen) har.
2026-07-22 11:31:08 +02:00
|
|
|
|
row = await conn.fetchrow("SELECT owner_user_id::text AS owner_user_id FROM round WHERE id = $1", round_id)
|
|
|
|
|
|
if row is None:
|
|
|
|
|
|
raise app_error(404, "NOT_FOUND", "Runden finnes ikke.")
|
|
|
|
|
|
if row["owner_user_id"] != user_id:
|
|
|
|
|
|
raise app_error(403, "NOT_AUTHORIZED", "Du eier ikke denne runden.")
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-07-26 08:22:41 +02:00
|
|
|
|
async def _get_accessible_round_or_404(conn, round_id: str, user_id: str):
|
|
|
|
|
|
"""Eier ELLER en lenket medspiller (round_participant.user_id) --
|
|
|
|
|
|
ADR-036 fase 3-utvidelsen (2026-07-26): en medspiller kan se runden og
|
|
|
|
|
|
registrere score for HELE flighten (bekreftet regel), men kan IKKE
|
|
|
|
|
|
forvalte runden (se `_get_owned_round_or_404` for det)."""
|
|
|
|
|
|
row = await conn.fetchrow(
|
|
|
|
|
|
"""
|
|
|
|
|
|
SELECT r.owner_user_id::text AS owner_user_id,
|
|
|
|
|
|
EXISTS(
|
|
|
|
|
|
SELECT 1 FROM round_participant
|
|
|
|
|
|
WHERE round_id = r.id AND user_id = $2
|
|
|
|
|
|
) AS is_linked_participant
|
|
|
|
|
|
FROM round r WHERE r.id = $1
|
|
|
|
|
|
""",
|
|
|
|
|
|
round_id,
|
|
|
|
|
|
user_id,
|
|
|
|
|
|
)
|
|
|
|
|
|
if row is None:
|
|
|
|
|
|
raise app_error(404, "NOT_FOUND", "Runden finnes ikke.")
|
|
|
|
|
|
if row["owner_user_id"] != user_id and not row["is_linked_participant"]:
|
|
|
|
|
|
raise app_error(403, "NOT_AUTHORIZED", "Du har ikke tilgang til denne runden.")
|
|
|
|
|
|
|
|
|
|
|
|
|
Migrasjonen er kjørt, og jeg har fortsatt helt til et komplett, verifisert API-lag.
Underveis fant og fikset jeg et reelt hull: round_participant manglet kolonner for selve rating-tallene (kun tee-navnet var lagret) — løst med migrasjon 021, kjørt mot ekte database sammen med resten.
Bygget og rullet ut:
app/routers/rounds.py — opprett/liste/hent/slett runde, legg til/fjern gjestedeltakere, registrer statistikk hull for hull, og en fullfør-endepunkt som kjører hele kjeden (Adjusted Gross Score → Score Differential → tellende-vurdering).
26 scratch-sjekker grønt, pluss en egen test mot den ekte kjørende teeoff-tjenesten som bekreftet at live-oppslaget fungerer nøyaktig som tiltenkt (ingen bane-kopi skrives noe sted).
Begge migrasjoner + ny kode er nå live mot ekte teecup_db/teecup_api. teeoff.no upåvirket.
Jeg la også inn /rounds og /personal-courses i frontendens proxy-oppsett proaktivt, siden prosjektet har blitt bitt av glemte proxy-ruter før — ikke deployet ennå, tas med neste gang frontend faktisk bygges.
Notatet om slaglengde-måling og avstand-til-punkter-på-banen er lagret i CLAUDE.md og FEATURE_BACKLOG.md, med en tydelig flagget avhengighet: dette krever geografiske/GPS-data som ingen nåværende kilde (verken teeoff eller den nye banekatalogen) har.
2026-07-22 11:31:08 +02:00
|
|
|
|
@router.get("/rounds/{round_id}", response_model=RoundOut)
|
|
|
|
|
|
async def get_round(round_id: str, user: CurrentUser = Depends(get_current_user)) -> RoundOut:
|
|
|
|
|
|
async with plain_connection() as conn:
|
2026-07-26 08:22:41 +02:00
|
|
|
|
await _get_accessible_round_or_404(conn, round_id, user.user_id)
|
|
|
|
|
|
return await _load_round_out(conn, round_id, user.user_id)
|
Migrasjonen er kjørt, og jeg har fortsatt helt til et komplett, verifisert API-lag.
Underveis fant og fikset jeg et reelt hull: round_participant manglet kolonner for selve rating-tallene (kun tee-navnet var lagret) — løst med migrasjon 021, kjørt mot ekte database sammen med resten.
Bygget og rullet ut:
app/routers/rounds.py — opprett/liste/hent/slett runde, legg til/fjern gjestedeltakere, registrer statistikk hull for hull, og en fullfør-endepunkt som kjører hele kjeden (Adjusted Gross Score → Score Differential → tellende-vurdering).
26 scratch-sjekker grønt, pluss en egen test mot den ekte kjørende teeoff-tjenesten som bekreftet at live-oppslaget fungerer nøyaktig som tiltenkt (ingen bane-kopi skrives noe sted).
Begge migrasjoner + ny kode er nå live mot ekte teecup_db/teecup_api. teeoff.no upåvirket.
Jeg la også inn /rounds og /personal-courses i frontendens proxy-oppsett proaktivt, siden prosjektet har blitt bitt av glemte proxy-ruter før — ikke deployet ennå, tas med neste gang frontend faktisk bygges.
Notatet om slaglengde-måling og avstand-til-punkter-på-banen er lagret i CLAUDE.md og FEATURE_BACKLOG.md, med en tydelig flagget avhengighet: dette krever geografiske/GPS-data som ingen nåværende kilde (verken teeoff eller den nye banekatalogen) har.
2026-07-22 11:31:08 +02:00
|
|
|
|
|
|
|
|
|
|
|
2026-07-24 11:21:39 +02:00
|
|
|
|
class RoundUpdate(BaseModel):
|
|
|
|
|
|
# Bane-bytte er ATOMISK -- enten oppgis course_source+tee_name (pluss de
|
|
|
|
|
|
# kildespesifikke feltene) sammen, eller ingen av dem. holes_planned/
|
|
|
|
|
|
# start_hole kan endres uavhengig, i samme kall eller alene -- disse
|
|
|
|
|
|
# rører aldri round_hole, kun visnings-/navigasjonsmetadata.
|
|
|
|
|
|
course_source: Literal["teeoff", "custom"] | None = None
|
|
|
|
|
|
teeoff_facility_slug: str | None = None
|
|
|
|
|
|
teeoff_course_id: int | None = None
|
|
|
|
|
|
personal_course_id: str | None = None
|
|
|
|
|
|
tee_name: str | None = None
|
2026-07-25 06:29:09 +02:00
|
|
|
|
# Rent metadata-felt -- kan endres/fjernes uansett fullført-status,
|
|
|
|
|
|
# samme begrunnelse som start_hole/started_at/completed_at under. Sendes
|
|
|
|
|
|
# feltet med tom streng, tolkes det som "fjern navnet" (NULL); mangler
|
|
|
|
|
|
# feltet i kallet, røres eksisterende navn ikke (samme mønster som de
|
|
|
|
|
|
# andre feltene her -- kun `None`/utelatt betyr "ikke rør").
|
|
|
|
|
|
name: str | None = Field(default=None, max_length=200)
|
2026-07-24 11:21:39 +02:00
|
|
|
|
holes_planned: Literal[9, 18] | None = None
|
|
|
|
|
|
start_hole: int | None = Field(default=None, ge=1, le=18)
|
|
|
|
|
|
# Utslagstidspunkt og "Ferdig"-tidspunkt -- begge kan justeres i
|
|
|
|
|
|
# etterkant (f.eks. glemte å trykke "Fullfør runde" før flere timer
|
|
|
|
|
|
# senere, og vil rette opp tidsbruken). completed_at kan KUN settes på
|
|
|
|
|
|
# en runde som allerede er fullført (se validering under) -- denne
|
|
|
|
|
|
# PATCH-en fullfører aldri runden selv, kun korrigerer et tidspunkt som
|
|
|
|
|
|
# /complete allerede har satt.
|
|
|
|
|
|
started_at: datetime | None = None
|
|
|
|
|
|
completed_at: datetime | None = None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.patch("/rounds/{round_id}", response_model=RoundOut)
|
|
|
|
|
|
async def update_round(
|
|
|
|
|
|
round_id: str,
|
|
|
|
|
|
body: RoundUpdate,
|
|
|
|
|
|
user: CurrentUser = Depends(get_current_user),
|
|
|
|
|
|
) -> RoundOut:
|
|
|
|
|
|
"""
|
|
|
|
|
|
Retter opp feil bane/utslag/antall hull/starthull ETTER at runden er
|
|
|
|
|
|
opprettet (2026-07-24) -- f.eks. hvis man oppdager underveis at feil
|
|
|
|
|
|
bane ble valgt, eller vil justere utslags-/fullført-tidspunktet i
|
|
|
|
|
|
etterkant. Endrer ALDRI allerede registrerte slag/putter/etc. i
|
|
|
|
|
|
round_hole -- kun rating-grunnlaget (par/stroke-index-snapshot +
|
|
|
|
|
|
course/slope-rating) hullene regnes ut fra. Bane-bytte og
|
|
|
|
|
|
holes_planned er bevisst avvist etter at runden er fullført
|
|
|
|
|
|
(differensialen er da allerede beregnet fra det gamle grunnlaget, og
|
|
|
|
|
|
en re-beregning etter fullføring er utenfor omfang her -- ulikt
|
|
|
|
|
|
tilsvarende bane-bytte for turnering-økter, som bevisst TILLATER dette
|
|
|
|
|
|
selv etter avgjørelse). start_hole/started_at/completed_at er derimot
|
|
|
|
|
|
ren metadata som ALDRI påvirker HCP-beregningen, og kan derfor justeres
|
|
|
|
|
|
uansett fullført-status.
|
|
|
|
|
|
"""
|
|
|
|
|
|
async with plain_connection() as conn:
|
|
|
|
|
|
await _get_owned_round_or_404(conn, round_id, user.user_id)
|
|
|
|
|
|
round_row = await conn.fetchrow("SELECT started_at, completed_at FROM round WHERE id = $1", round_id)
|
|
|
|
|
|
is_completed = round_row["completed_at"] is not None
|
|
|
|
|
|
|
|
|
|
|
|
if is_completed and (body.course_source is not None or body.holes_planned is not None):
|
|
|
|
|
|
raise app_error(
|
|
|
|
|
|
409, "ALREADY_COMPLETED",
|
|
|
|
|
|
"Runden er allerede fullført -- kan ikke endre bane eller antall hull i etterkant.",
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
if body.completed_at is not None and not is_completed:
|
|
|
|
|
|
raise app_error(
|
|
|
|
|
|
400, "VALIDATION_FAILED",
|
|
|
|
|
|
"Runden må fullføres via \"Fullfør runde\" først -- deretter kan tidspunktet justeres.",
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
effective_started_at = body.started_at if body.started_at is not None else round_row["started_at"]
|
|
|
|
|
|
effective_completed_at = body.completed_at if body.completed_at is not None else round_row["completed_at"]
|
|
|
|
|
|
if (
|
|
|
|
|
|
effective_started_at is not None
|
|
|
|
|
|
and effective_completed_at is not None
|
|
|
|
|
|
and effective_completed_at <= effective_started_at
|
|
|
|
|
|
):
|
|
|
|
|
|
raise app_error(400, "VALIDATION_FAILED", "Fullført-tidspunktet må være etter utslagstidspunktet.")
|
|
|
|
|
|
|
|
|
|
|
|
resolved: _ResolvedCourse | None = None
|
|
|
|
|
|
participants = None
|
|
|
|
|
|
if body.course_source is not None:
|
|
|
|
|
|
if not body.tee_name:
|
|
|
|
|
|
raise app_error(400, "VALIDATION_FAILED", "tee_name er påkrevd ved bane-bytte.")
|
|
|
|
|
|
if body.course_source == "teeoff":
|
|
|
|
|
|
if not body.teeoff_facility_slug or not body.teeoff_course_id:
|
|
|
|
|
|
raise app_error(400, "VALIDATION_FAILED", "teeoff_facility_slug og teeoff_course_id er påkrevd.")
|
|
|
|
|
|
resolved = await _resolve_teeoff_course(body.teeoff_facility_slug, body.teeoff_course_id)
|
|
|
|
|
|
else:
|
|
|
|
|
|
if not body.personal_course_id:
|
|
|
|
|
|
raise app_error(400, "VALIDATION_FAILED", "personal_course_id er påkrevd.")
|
|
|
|
|
|
resolved = await _resolve_personal_course(body.personal_course_id)
|
|
|
|
|
|
|
|
|
|
|
|
participants = await conn.fetch(
|
|
|
|
|
|
"""
|
|
|
|
|
|
SELECT id::text AS id, gender, handicap_index_snapshot::float AS handicap_index_snapshot
|
|
|
|
|
|
FROM round_participant WHERE round_id = $1
|
|
|
|
|
|
""",
|
|
|
|
|
|
round_id,
|
|
|
|
|
|
)
|
|
|
|
|
|
# Valider FOR ALLE deltakere FØR noe skrives -- hele bane-byttet
|
|
|
|
|
|
# avvises tydelig hvis ÉN eneste deltaker ville mistet HCP-
|
|
|
|
|
|
# sporing, ingen delvis anvendt endring.
|
|
|
|
|
|
for p in participants:
|
|
|
|
|
|
if resolved.rating_for(body.tee_name, p["gender"]) is None:
|
|
|
|
|
|
raise app_error(
|
|
|
|
|
|
400, "VALIDATION_FAILED",
|
|
|
|
|
|
"Den nye banen/utslaget mangler rating for én eller flere av deltakernes kjønn -- ingenting er endret.",
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
async with conn.transaction():
|
|
|
|
|
|
if body.holes_planned is not None:
|
|
|
|
|
|
await conn.execute("UPDATE round SET holes_planned = $2 WHERE id = $1", round_id, body.holes_planned)
|
|
|
|
|
|
if body.start_hole is not None:
|
|
|
|
|
|
await conn.execute("UPDATE round SET start_hole = $2 WHERE id = $1", round_id, body.start_hole)
|
2026-07-25 06:29:09 +02:00
|
|
|
|
if body.name is not None:
|
|
|
|
|
|
await conn.execute(
|
|
|
|
|
|
"UPDATE round SET name = $2 WHERE id = $1",
|
|
|
|
|
|
round_id,
|
|
|
|
|
|
body.name.strip() if body.name.strip() else None,
|
|
|
|
|
|
)
|
2026-07-24 11:21:39 +02:00
|
|
|
|
if body.started_at is not None:
|
|
|
|
|
|
await conn.execute("UPDATE round SET started_at = $2 WHERE id = $1", round_id, body.started_at)
|
|
|
|
|
|
if body.completed_at is not None:
|
|
|
|
|
|
await conn.execute("UPDATE round SET completed_at = $2 WHERE id = $1", round_id, body.completed_at)
|
|
|
|
|
|
|
|
|
|
|
|
if resolved is not None and participants is not None:
|
|
|
|
|
|
await conn.execute(
|
|
|
|
|
|
"""
|
|
|
|
|
|
UPDATE round SET course_source = $2, teeoff_facility_slug = $3, teeoff_course_id = $4,
|
|
|
|
|
|
personal_course_id = $5, course_name_snapshot = $6, tee_name_snapshot = $7
|
|
|
|
|
|
WHERE id = $1
|
|
|
|
|
|
""",
|
|
|
|
|
|
round_id,
|
|
|
|
|
|
body.course_source,
|
|
|
|
|
|
body.teeoff_facility_slug,
|
|
|
|
|
|
str(body.teeoff_course_id) if body.teeoff_course_id is not None else None,
|
|
|
|
|
|
body.personal_course_id,
|
|
|
|
|
|
resolved.course_name,
|
|
|
|
|
|
body.tee_name,
|
|
|
|
|
|
)
|
|
|
|
|
|
par_by_hole = {h[0]: h[1] for h in resolved.holes}
|
|
|
|
|
|
index_by_hole = {h[0]: h[2] for h in resolved.holes}
|
|
|
|
|
|
for p in participants:
|
|
|
|
|
|
course_rating, slope_rating, tee_par = resolved.rating_for(body.tee_name, p["gender"])
|
|
|
|
|
|
course_handicap_snapshot = None
|
|
|
|
|
|
if p["handicap_index_snapshot"] is not None:
|
|
|
|
|
|
course_handicap_snapshot = course_handicap(
|
|
|
|
|
|
p["handicap_index_snapshot"], slope_rating, course_rating, tee_par
|
|
|
|
|
|
)
|
|
|
|
|
|
await conn.execute(
|
|
|
|
|
|
"""
|
2026-07-26 09:18:52 +02:00
|
|
|
|
UPDATE round_participant SET tee_name_snapshot = $2, course_rating_snapshot = $3,
|
|
|
|
|
|
slope_rating_snapshot = $4, tee_par_snapshot = $5, course_handicap_snapshot = $6
|
2026-07-24 11:21:39 +02:00
|
|
|
|
WHERE id = $1
|
|
|
|
|
|
""",
|
2026-07-26 09:18:52 +02:00
|
|
|
|
p["id"], body.tee_name, course_rating, slope_rating, tee_par, course_handicap_snapshot,
|
2026-07-24 11:21:39 +02:00
|
|
|
|
)
|
|
|
|
|
|
for hole_number in range(1, 19):
|
|
|
|
|
|
await conn.execute(
|
|
|
|
|
|
"""
|
|
|
|
|
|
UPDATE round_hole SET par = $3, stroke_index = $4
|
|
|
|
|
|
WHERE round_participant_id = $1 AND hole_number = $2
|
|
|
|
|
|
""",
|
|
|
|
|
|
p["id"], hole_number, par_by_hole[hole_number], index_by_hole[hole_number],
|
|
|
|
|
|
)
|
|
|
|
|
|
|
2026-07-26 08:49:37 +02:00
|
|
|
|
result = await _load_round_out(conn, round_id, user.user_id)
|
|
|
|
|
|
|
|
|
|
|
|
await broadcast_round_update(round_id)
|
|
|
|
|
|
return result
|
2026-07-24 11:21:39 +02:00
|
|
|
|
|
|
|
|
|
|
|
Migrasjonen er kjørt, og jeg har fortsatt helt til et komplett, verifisert API-lag.
Underveis fant og fikset jeg et reelt hull: round_participant manglet kolonner for selve rating-tallene (kun tee-navnet var lagret) — løst med migrasjon 021, kjørt mot ekte database sammen med resten.
Bygget og rullet ut:
app/routers/rounds.py — opprett/liste/hent/slett runde, legg til/fjern gjestedeltakere, registrer statistikk hull for hull, og en fullfør-endepunkt som kjører hele kjeden (Adjusted Gross Score → Score Differential → tellende-vurdering).
26 scratch-sjekker grønt, pluss en egen test mot den ekte kjørende teeoff-tjenesten som bekreftet at live-oppslaget fungerer nøyaktig som tiltenkt (ingen bane-kopi skrives noe sted).
Begge migrasjoner + ny kode er nå live mot ekte teecup_db/teecup_api. teeoff.no upåvirket.
Jeg la også inn /rounds og /personal-courses i frontendens proxy-oppsett proaktivt, siden prosjektet har blitt bitt av glemte proxy-ruter før — ikke deployet ennå, tas med neste gang frontend faktisk bygges.
Notatet om slaglengde-måling og avstand-til-punkter-på-banen er lagret i CLAUDE.md og FEATURE_BACKLOG.md, med en tydelig flagget avhengighet: dette krever geografiske/GPS-data som ingen nåværende kilde (verken teeoff eller den nye banekatalogen) har.
2026-07-22 11:31:08 +02:00
|
|
|
|
@router.delete("/rounds/{round_id}", status_code=204)
|
|
|
|
|
|
async def delete_round(round_id: str, user: CurrentUser = Depends(get_current_user)) -> None:
|
|
|
|
|
|
async with plain_connection() as conn:
|
|
|
|
|
|
await _get_owned_round_or_404(conn, round_id, user.user_id)
|
|
|
|
|
|
await conn.execute("DELETE FROM round WHERE id = $1", round_id)
|
2026-07-26 08:49:37 +02:00
|
|
|
|
# Varsler evt. tilkoblede medspillere om at runden er borte -- deres
|
|
|
|
|
|
# neste refetch vil da naturlig treffe 404 og vise en tydelig feil.
|
|
|
|
|
|
await broadcast_round_update(round_id)
|
Migrasjonen er kjørt, og jeg har fortsatt helt til et komplett, verifisert API-lag.
Underveis fant og fikset jeg et reelt hull: round_participant manglet kolonner for selve rating-tallene (kun tee-navnet var lagret) — løst med migrasjon 021, kjørt mot ekte database sammen med resten.
Bygget og rullet ut:
app/routers/rounds.py — opprett/liste/hent/slett runde, legg til/fjern gjestedeltakere, registrer statistikk hull for hull, og en fullfør-endepunkt som kjører hele kjeden (Adjusted Gross Score → Score Differential → tellende-vurdering).
26 scratch-sjekker grønt, pluss en egen test mot den ekte kjørende teeoff-tjenesten som bekreftet at live-oppslaget fungerer nøyaktig som tiltenkt (ingen bane-kopi skrives noe sted).
Begge migrasjoner + ny kode er nå live mot ekte teecup_db/teecup_api. teeoff.no upåvirket.
Jeg la også inn /rounds og /personal-courses i frontendens proxy-oppsett proaktivt, siden prosjektet har blitt bitt av glemte proxy-ruter før — ikke deployet ennå, tas med neste gang frontend faktisk bygges.
Notatet om slaglengde-måling og avstand-til-punkter-på-banen er lagret i CLAUDE.md og FEATURE_BACKLOG.md, med en tydelig flagget avhengighet: dette krever geografiske/GPS-data som ingen nåværende kilde (verken teeoff eller den nye banekatalogen) har.
2026-07-22 11:31:08 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
2026-07-26 08:22:41 +02:00
|
|
|
|
# Medspillere (flighten) -- enten en EKTE TeeCup-bruker (funnet via det
|
|
|
|
|
|
# delte tiered personsøket, /people/search, ADR-036) eller en gjest uten
|
|
|
|
|
|
# konto (fritekstnavn). ADR-036 fase 3-utvidelsen (2026-07-26) -- inntil da
|
|
|
|
|
|
# var alle deltakere utenom eieren gjester.
|
Migrasjonen er kjørt, og jeg har fortsatt helt til et komplett, verifisert API-lag.
Underveis fant og fikset jeg et reelt hull: round_participant manglet kolonner for selve rating-tallene (kun tee-navnet var lagret) — løst med migrasjon 021, kjørt mot ekte database sammen med resten.
Bygget og rullet ut:
app/routers/rounds.py — opprett/liste/hent/slett runde, legg til/fjern gjestedeltakere, registrer statistikk hull for hull, og en fullfør-endepunkt som kjører hele kjeden (Adjusted Gross Score → Score Differential → tellende-vurdering).
26 scratch-sjekker grønt, pluss en egen test mot den ekte kjørende teeoff-tjenesten som bekreftet at live-oppslaget fungerer nøyaktig som tiltenkt (ingen bane-kopi skrives noe sted).
Begge migrasjoner + ny kode er nå live mot ekte teecup_db/teecup_api. teeoff.no upåvirket.
Jeg la også inn /rounds og /personal-courses i frontendens proxy-oppsett proaktivt, siden prosjektet har blitt bitt av glemte proxy-ruter før — ikke deployet ennå, tas med neste gang frontend faktisk bygges.
Notatet om slaglengde-måling og avstand-til-punkter-på-banen er lagret i CLAUDE.md og FEATURE_BACKLOG.md, med en tydelig flagget avhengighet: dette krever geografiske/GPS-data som ingen nåværende kilde (verken teeoff eller den nye banekatalogen) har.
2026-07-22 11:31:08 +02:00
|
|
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
|
2026-07-26 08:22:41 +02:00
|
|
|
|
class ParticipantCreate(BaseModel):
|
|
|
|
|
|
# Nøyaktig én av user_id/guest_name.
|
|
|
|
|
|
user_id: str | None = None
|
|
|
|
|
|
guest_name: str | None = Field(default=None, min_length=1, max_length=100)
|
|
|
|
|
|
# Kun brukt for guest_name-varianten -- for user_id hentes kjønn/HCP fra
|
|
|
|
|
|
# personens egen profil (samme kilde som eierens egen deltaker-rad).
|
|
|
|
|
|
gender: Literal["m", "f", "x"] | None = None
|
Migrasjonen er kjørt, og jeg har fortsatt helt til et komplett, verifisert API-lag.
Underveis fant og fikset jeg et reelt hull: round_participant manglet kolonner for selve rating-tallene (kun tee-navnet var lagret) — løst med migrasjon 021, kjørt mot ekte database sammen med resten.
Bygget og rullet ut:
app/routers/rounds.py — opprett/liste/hent/slett runde, legg til/fjern gjestedeltakere, registrer statistikk hull for hull, og en fullfør-endepunkt som kjører hele kjeden (Adjusted Gross Score → Score Differential → tellende-vurdering).
26 scratch-sjekker grønt, pluss en egen test mot den ekte kjørende teeoff-tjenesten som bekreftet at live-oppslaget fungerer nøyaktig som tiltenkt (ingen bane-kopi skrives noe sted).
Begge migrasjoner + ny kode er nå live mot ekte teecup_db/teecup_api. teeoff.no upåvirket.
Jeg la også inn /rounds og /personal-courses i frontendens proxy-oppsett proaktivt, siden prosjektet har blitt bitt av glemte proxy-ruter før — ikke deployet ennå, tas med neste gang frontend faktisk bygges.
Notatet om slaglengde-måling og avstand-til-punkter-på-banen er lagret i CLAUDE.md og FEATURE_BACKLOG.md, med en tydelig flagget avhengighet: dette krever geografiske/GPS-data som ingen nåværende kilde (verken teeoff eller den nye banekatalogen) har.
2026-07-22 11:31:08 +02:00
|
|
|
|
handicap_index: float | None = Field(default=None, ge=-10, le=54)
|
2026-07-26 09:42:06 +02:00
|
|
|
|
# Valgfritt kontaktfelt for en gjest (2026-07-26) -- avvist hvis oppgitt
|
|
|
|
|
|
# sammen med user_id (en lenket bruker har allerede sin egen e-post).
|
|
|
|
|
|
guest_email: EmailStr | None = None
|
2026-07-26 09:18:52 +02:00
|
|
|
|
# Utslagssted for DENNE deltakeren (2026-07-26) -- valgfritt, faller
|
|
|
|
|
|
# tilbake til rundens eget utslag (samme som før) hvis utelatt. Kan
|
|
|
|
|
|
# avvike fra de andre deltakerne, f.eks. et kjønnsdelt utslag.
|
|
|
|
|
|
tee_name: str | None = None
|
2026-07-24 06:27:35 +02:00
|
|
|
|
stat_level: StatLevel = "strokes_only"
|
Migrasjonen er kjørt, og jeg har fortsatt helt til et komplett, verifisert API-lag.
Underveis fant og fikset jeg et reelt hull: round_participant manglet kolonner for selve rating-tallene (kun tee-navnet var lagret) — løst med migrasjon 021, kjørt mot ekte database sammen med resten.
Bygget og rullet ut:
app/routers/rounds.py — opprett/liste/hent/slett runde, legg til/fjern gjestedeltakere, registrer statistikk hull for hull, og en fullfør-endepunkt som kjører hele kjeden (Adjusted Gross Score → Score Differential → tellende-vurdering).
26 scratch-sjekker grønt, pluss en egen test mot den ekte kjørende teeoff-tjenesten som bekreftet at live-oppslaget fungerer nøyaktig som tiltenkt (ingen bane-kopi skrives noe sted).
Begge migrasjoner + ny kode er nå live mot ekte teecup_db/teecup_api. teeoff.no upåvirket.
Jeg la også inn /rounds og /personal-courses i frontendens proxy-oppsett proaktivt, siden prosjektet har blitt bitt av glemte proxy-ruter før — ikke deployet ennå, tas med neste gang frontend faktisk bygges.
Notatet om slaglengde-måling og avstand-til-punkter-på-banen er lagret i CLAUDE.md og FEATURE_BACKLOG.md, med en tydelig flagget avhengighet: dette krever geografiske/GPS-data som ingen nåværende kilde (verken teeoff eller den nye banekatalogen) har.
2026-07-22 11:31:08 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.post("/rounds/{round_id}/participants", response_model=RoundParticipantOut, status_code=201)
|
2026-07-26 08:22:41 +02:00
|
|
|
|
async def add_participant(
|
Migrasjonen er kjørt, og jeg har fortsatt helt til et komplett, verifisert API-lag.
Underveis fant og fikset jeg et reelt hull: round_participant manglet kolonner for selve rating-tallene (kun tee-navnet var lagret) — løst med migrasjon 021, kjørt mot ekte database sammen med resten.
Bygget og rullet ut:
app/routers/rounds.py — opprett/liste/hent/slett runde, legg til/fjern gjestedeltakere, registrer statistikk hull for hull, og en fullfør-endepunkt som kjører hele kjeden (Adjusted Gross Score → Score Differential → tellende-vurdering).
26 scratch-sjekker grønt, pluss en egen test mot den ekte kjørende teeoff-tjenesten som bekreftet at live-oppslaget fungerer nøyaktig som tiltenkt (ingen bane-kopi skrives noe sted).
Begge migrasjoner + ny kode er nå live mot ekte teecup_db/teecup_api. teeoff.no upåvirket.
Jeg la også inn /rounds og /personal-courses i frontendens proxy-oppsett proaktivt, siden prosjektet har blitt bitt av glemte proxy-ruter før — ikke deployet ennå, tas med neste gang frontend faktisk bygges.
Notatet om slaglengde-måling og avstand-til-punkter-på-banen er lagret i CLAUDE.md og FEATURE_BACKLOG.md, med en tydelig flagget avhengighet: dette krever geografiske/GPS-data som ingen nåværende kilde (verken teeoff eller den nye banekatalogen) har.
2026-07-22 11:31:08 +02:00
|
|
|
|
round_id: str,
|
2026-07-26 08:22:41 +02:00
|
|
|
|
body: ParticipantCreate,
|
Migrasjonen er kjørt, og jeg har fortsatt helt til et komplett, verifisert API-lag.
Underveis fant og fikset jeg et reelt hull: round_participant manglet kolonner for selve rating-tallene (kun tee-navnet var lagret) — løst med migrasjon 021, kjørt mot ekte database sammen med resten.
Bygget og rullet ut:
app/routers/rounds.py — opprett/liste/hent/slett runde, legg til/fjern gjestedeltakere, registrer statistikk hull for hull, og en fullfør-endepunkt som kjører hele kjeden (Adjusted Gross Score → Score Differential → tellende-vurdering).
26 scratch-sjekker grønt, pluss en egen test mot den ekte kjørende teeoff-tjenesten som bekreftet at live-oppslaget fungerer nøyaktig som tiltenkt (ingen bane-kopi skrives noe sted).
Begge migrasjoner + ny kode er nå live mot ekte teecup_db/teecup_api. teeoff.no upåvirket.
Jeg la også inn /rounds og /personal-courses i frontendens proxy-oppsett proaktivt, siden prosjektet har blitt bitt av glemte proxy-ruter før — ikke deployet ennå, tas med neste gang frontend faktisk bygges.
Notatet om slaglengde-måling og avstand-til-punkter-på-banen er lagret i CLAUDE.md og FEATURE_BACKLOG.md, med en tydelig flagget avhengighet: dette krever geografiske/GPS-data som ingen nåværende kilde (verken teeoff eller den nye banekatalogen) har.
2026-07-22 11:31:08 +02:00
|
|
|
|
user: CurrentUser = Depends(get_current_user),
|
|
|
|
|
|
) -> RoundParticipantOut:
|
2026-07-26 08:22:41 +02:00
|
|
|
|
if (body.user_id is not None) == (body.guest_name is not None):
|
|
|
|
|
|
raise app_error(400, "VALIDATION_FAILED", "Oppgi enten user_id (funnet via søk) eller guest_name, ikke begge/ingen.")
|
2026-07-26 09:42:06 +02:00
|
|
|
|
if body.user_id is not None and body.guest_email is not None:
|
|
|
|
|
|
raise app_error(400, "VALIDATION_FAILED", "guest_email gir ikke mening sammen med user_id -- en lenket bruker har allerede sin egen konto-e-post.")
|
2026-07-26 08:22:41 +02:00
|
|
|
|
|
Migrasjonen er kjørt, og jeg har fortsatt helt til et komplett, verifisert API-lag.
Underveis fant og fikset jeg et reelt hull: round_participant manglet kolonner for selve rating-tallene (kun tee-navnet var lagret) — løst med migrasjon 021, kjørt mot ekte database sammen med resten.
Bygget og rullet ut:
app/routers/rounds.py — opprett/liste/hent/slett runde, legg til/fjern gjestedeltakere, registrer statistikk hull for hull, og en fullfør-endepunkt som kjører hele kjeden (Adjusted Gross Score → Score Differential → tellende-vurdering).
26 scratch-sjekker grønt, pluss en egen test mot den ekte kjørende teeoff-tjenesten som bekreftet at live-oppslaget fungerer nøyaktig som tiltenkt (ingen bane-kopi skrives noe sted).
Begge migrasjoner + ny kode er nå live mot ekte teecup_db/teecup_api. teeoff.no upåvirket.
Jeg la også inn /rounds og /personal-courses i frontendens proxy-oppsett proaktivt, siden prosjektet har blitt bitt av glemte proxy-ruter før — ikke deployet ennå, tas med neste gang frontend faktisk bygges.
Notatet om slaglengde-måling og avstand-til-punkter-på-banen er lagret i CLAUDE.md og FEATURE_BACKLOG.md, med en tydelig flagget avhengighet: dette krever geografiske/GPS-data som ingen nåværende kilde (verken teeoff eller den nye banekatalogen) har.
2026-07-22 11:31:08 +02:00
|
|
|
|
async with plain_connection() as conn:
|
|
|
|
|
|
round_row = await conn.fetchrow(
|
2026-07-26 08:22:41 +02:00
|
|
|
|
"SELECT owner_user_id::text AS owner_user_id, course_source, teeoff_facility_slug, "
|
|
|
|
|
|
"teeoff_course_id, personal_course_id, tee_name_snapshot FROM round WHERE id = $1",
|
Migrasjonen er kjørt, og jeg har fortsatt helt til et komplett, verifisert API-lag.
Underveis fant og fikset jeg et reelt hull: round_participant manglet kolonner for selve rating-tallene (kun tee-navnet var lagret) — løst med migrasjon 021, kjørt mot ekte database sammen med resten.
Bygget og rullet ut:
app/routers/rounds.py — opprett/liste/hent/slett runde, legg til/fjern gjestedeltakere, registrer statistikk hull for hull, og en fullfør-endepunkt som kjører hele kjeden (Adjusted Gross Score → Score Differential → tellende-vurdering).
26 scratch-sjekker grønt, pluss en egen test mot den ekte kjørende teeoff-tjenesten som bekreftet at live-oppslaget fungerer nøyaktig som tiltenkt (ingen bane-kopi skrives noe sted).
Begge migrasjoner + ny kode er nå live mot ekte teecup_db/teecup_api. teeoff.no upåvirket.
Jeg la også inn /rounds og /personal-courses i frontendens proxy-oppsett proaktivt, siden prosjektet har blitt bitt av glemte proxy-ruter før — ikke deployet ennå, tas med neste gang frontend faktisk bygges.
Notatet om slaglengde-måling og avstand-til-punkter-på-banen er lagret i CLAUDE.md og FEATURE_BACKLOG.md, med en tydelig flagget avhengighet: dette krever geografiske/GPS-data som ingen nåværende kilde (verken teeoff eller den nye banekatalogen) har.
2026-07-22 11:31:08 +02:00
|
|
|
|
round_id,
|
|
|
|
|
|
)
|
2026-07-26 08:22:41 +02:00
|
|
|
|
if round_row is None:
|
|
|
|
|
|
raise app_error(404, "NOT_FOUND", "Runden finnes ikke.")
|
|
|
|
|
|
if round_row["owner_user_id"] != user.user_id:
|
|
|
|
|
|
raise app_error(403, "NOT_AUTHORIZED", "Du eier ikke denne runden.")
|
|
|
|
|
|
|
Migrasjonen er kjørt, og jeg har fortsatt helt til et komplett, verifisert API-lag.
Underveis fant og fikset jeg et reelt hull: round_participant manglet kolonner for selve rating-tallene (kun tee-navnet var lagret) — løst med migrasjon 021, kjørt mot ekte database sammen med resten.
Bygget og rullet ut:
app/routers/rounds.py — opprett/liste/hent/slett runde, legg til/fjern gjestedeltakere, registrer statistikk hull for hull, og en fullfør-endepunkt som kjører hele kjeden (Adjusted Gross Score → Score Differential → tellende-vurdering).
26 scratch-sjekker grønt, pluss en egen test mot den ekte kjørende teeoff-tjenesten som bekreftet at live-oppslaget fungerer nøyaktig som tiltenkt (ingen bane-kopi skrives noe sted).
Begge migrasjoner + ny kode er nå live mot ekte teecup_db/teecup_api. teeoff.no upåvirket.
Jeg la også inn /rounds og /personal-courses i frontendens proxy-oppsett proaktivt, siden prosjektet har blitt bitt av glemte proxy-ruter før — ikke deployet ennå, tas med neste gang frontend faktisk bygges.
Notatet om slaglengde-måling og avstand-til-punkter-på-banen er lagret i CLAUDE.md og FEATURE_BACKLOG.md, med en tydelig flagget avhengighet: dette krever geografiske/GPS-data som ingen nåværende kilde (verken teeoff eller den nye banekatalogen) har.
2026-07-22 11:31:08 +02:00
|
|
|
|
if round_row["course_source"] == "teeoff":
|
|
|
|
|
|
resolved = await _resolve_teeoff_course(round_row["teeoff_facility_slug"], int(round_row["teeoff_course_id"]))
|
|
|
|
|
|
else:
|
|
|
|
|
|
resolved = await _resolve_personal_course(round_row["personal_course_id"])
|
|
|
|
|
|
|
2026-07-26 08:22:41 +02:00
|
|
|
|
if body.user_id is not None:
|
|
|
|
|
|
if body.user_id == round_row["owner_user_id"]:
|
|
|
|
|
|
raise app_error(400, "VALIDATION_FAILED", "Du er allerede med i runden som eier.")
|
|
|
|
|
|
target = await conn.fetchrow(
|
|
|
|
|
|
"SELECT gender, handicap_index::float AS handicap_index FROM app_user WHERE id = $1",
|
|
|
|
|
|
body.user_id,
|
|
|
|
|
|
)
|
|
|
|
|
|
if target is None:
|
|
|
|
|
|
raise app_error(404, "NOT_FOUND", "Fant ikke brukeren.")
|
|
|
|
|
|
if target["gender"] is None:
|
|
|
|
|
|
raise app_error(
|
|
|
|
|
|
400, "VALIDATION_FAILED",
|
|
|
|
|
|
"Spilleren du valgte har ikke fullført profilen sin (mangler kjønn) og kan ikke legges til ennå.",
|
|
|
|
|
|
)
|
|
|
|
|
|
gender = target["gender"]
|
|
|
|
|
|
handicap_index = target["handicap_index"]
|
|
|
|
|
|
guest_name = None
|
|
|
|
|
|
linked_user_id = body.user_id
|
|
|
|
|
|
else:
|
|
|
|
|
|
gender = body.gender
|
|
|
|
|
|
handicap_index = body.handicap_index
|
|
|
|
|
|
guest_name = body.guest_name.strip()
|
|
|
|
|
|
linked_user_id = None
|
|
|
|
|
|
if gender is None:
|
|
|
|
|
|
raise app_error(400, "VALIDATION_FAILED", "gender er påkrevd for en gjest uten konto.")
|
|
|
|
|
|
|
2026-07-26 09:18:52 +02:00
|
|
|
|
tee_name = body.tee_name or round_row["tee_name_snapshot"]
|
|
|
|
|
|
if resolved.rating_for(tee_name, gender) is None and handicap_index is not None:
|
Migrasjonen er kjørt, og jeg har fortsatt helt til et komplett, verifisert API-lag.
Underveis fant og fikset jeg et reelt hull: round_participant manglet kolonner for selve rating-tallene (kun tee-navnet var lagret) — løst med migrasjon 021, kjørt mot ekte database sammen med resten.
Bygget og rullet ut:
app/routers/rounds.py — opprett/liste/hent/slett runde, legg til/fjern gjestedeltakere, registrer statistikk hull for hull, og en fullfør-endepunkt som kjører hele kjeden (Adjusted Gross Score → Score Differential → tellende-vurdering).
26 scratch-sjekker grønt, pluss en egen test mot den ekte kjørende teeoff-tjenesten som bekreftet at live-oppslaget fungerer nøyaktig som tiltenkt (ingen bane-kopi skrives noe sted).
Begge migrasjoner + ny kode er nå live mot ekte teecup_db/teecup_api. teeoff.no upåvirket.
Jeg la også inn /rounds og /personal-courses i frontendens proxy-oppsett proaktivt, siden prosjektet har blitt bitt av glemte proxy-ruter før — ikke deployet ennå, tas med neste gang frontend faktisk bygges.
Notatet om slaglengde-måling og avstand-til-punkter-på-banen er lagret i CLAUDE.md og FEATURE_BACKLOG.md, med en tydelig flagget avhengighet: dette krever geografiske/GPS-data som ingen nåværende kilde (verken teeoff eller den nye banekatalogen) har.
2026-07-22 11:31:08 +02:00
|
|
|
|
raise app_error(
|
|
|
|
|
|
400, "VALIDATION_FAILED",
|
2026-07-26 09:18:52 +02:00
|
|
|
|
"Valgt utslag har ingen rating for dette kjønnet -- HCP-sporing er ikke mulig for denne deltakeren.",
|
Migrasjonen er kjørt, og jeg har fortsatt helt til et komplett, verifisert API-lag.
Underveis fant og fikset jeg et reelt hull: round_participant manglet kolonner for selve rating-tallene (kun tee-navnet var lagret) — løst med migrasjon 021, kjørt mot ekte database sammen med resten.
Bygget og rullet ut:
app/routers/rounds.py — opprett/liste/hent/slett runde, legg til/fjern gjestedeltakere, registrer statistikk hull for hull, og en fullfør-endepunkt som kjører hele kjeden (Adjusted Gross Score → Score Differential → tellende-vurdering).
26 scratch-sjekker grønt, pluss en egen test mot den ekte kjørende teeoff-tjenesten som bekreftet at live-oppslaget fungerer nøyaktig som tiltenkt (ingen bane-kopi skrives noe sted).
Begge migrasjoner + ny kode er nå live mot ekte teecup_db/teecup_api. teeoff.no upåvirket.
Jeg la også inn /rounds og /personal-courses i frontendens proxy-oppsett proaktivt, siden prosjektet har blitt bitt av glemte proxy-ruter før — ikke deployet ennå, tas med neste gang frontend faktisk bygges.
Notatet om slaglengde-måling og avstand-til-punkter-på-banen er lagret i CLAUDE.md og FEATURE_BACKLOG.md, med en tydelig flagget avhengighet: dette krever geografiske/GPS-data som ingen nåværende kilde (verken teeoff eller den nye banekatalogen) har.
2026-07-22 11:31:08 +02:00
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
async with conn.transaction(), translate_db_errors():
|
|
|
|
|
|
participant_id = await _create_participant(
|
2026-07-26 09:18:52 +02:00
|
|
|
|
conn, round_id, resolved, tee_name,
|
2026-07-26 08:22:41 +02:00
|
|
|
|
user_id=linked_user_id, guest_name=guest_name, is_owner=False,
|
|
|
|
|
|
gender=gender, handicap_index=handicap_index,
|
2026-07-24 06:27:35 +02:00
|
|
|
|
stat_level=body.stat_level,
|
2026-07-26 09:42:06 +02:00
|
|
|
|
guest_email=body.guest_email,
|
Migrasjonen er kjørt, og jeg har fortsatt helt til et komplett, verifisert API-lag.
Underveis fant og fikset jeg et reelt hull: round_participant manglet kolonner for selve rating-tallene (kun tee-navnet var lagret) — løst med migrasjon 021, kjørt mot ekte database sammen med resten.
Bygget og rullet ut:
app/routers/rounds.py — opprett/liste/hent/slett runde, legg til/fjern gjestedeltakere, registrer statistikk hull for hull, og en fullfør-endepunkt som kjører hele kjeden (Adjusted Gross Score → Score Differential → tellende-vurdering).
26 scratch-sjekker grønt, pluss en egen test mot den ekte kjørende teeoff-tjenesten som bekreftet at live-oppslaget fungerer nøyaktig som tiltenkt (ingen bane-kopi skrives noe sted).
Begge migrasjoner + ny kode er nå live mot ekte teecup_db/teecup_api. teeoff.no upåvirket.
Jeg la også inn /rounds og /personal-courses i frontendens proxy-oppsett proaktivt, siden prosjektet har blitt bitt av glemte proxy-ruter før — ikke deployet ennå, tas med neste gang frontend faktisk bygges.
Notatet om slaglengde-måling og avstand-til-punkter-på-banen er lagret i CLAUDE.md og FEATURE_BACKLOG.md, med en tydelig flagget avhengighet: dette krever geografiske/GPS-data som ingen nåværende kilde (verken teeoff eller den nye banekatalogen) har.
2026-07-22 11:31:08 +02:00
|
|
|
|
)
|
|
|
|
|
|
row = await conn.fetchrow(
|
|
|
|
|
|
"""
|
2026-07-26 09:42:06 +02:00
|
|
|
|
SELECT rp.id::text AS id, rp.user_id::text AS user_id, rp.guest_name, rp.guest_email,
|
2026-07-26 08:22:41 +02:00
|
|
|
|
COALESCE(rp.guest_name, au.display_name, 'Medspiller') AS display_name,
|
2026-07-26 09:18:52 +02:00
|
|
|
|
rp.is_owner, rp.gender, rp.tee_name_snapshot,
|
2026-07-26 08:22:41 +02:00
|
|
|
|
rp.handicap_index_snapshot::float AS handicap_index_snapshot,
|
|
|
|
|
|
rp.course_handicap_snapshot, rp.counts_for_handicap,
|
|
|
|
|
|
rp.score_differential::float AS score_differential, rp.stat_level
|
|
|
|
|
|
FROM round_participant rp
|
|
|
|
|
|
LEFT JOIN app_user au ON au.id = rp.user_id
|
|
|
|
|
|
WHERE rp.id = $1
|
Migrasjonen er kjørt, og jeg har fortsatt helt til et komplett, verifisert API-lag.
Underveis fant og fikset jeg et reelt hull: round_participant manglet kolonner for selve rating-tallene (kun tee-navnet var lagret) — løst med migrasjon 021, kjørt mot ekte database sammen med resten.
Bygget og rullet ut:
app/routers/rounds.py — opprett/liste/hent/slett runde, legg til/fjern gjestedeltakere, registrer statistikk hull for hull, og en fullfør-endepunkt som kjører hele kjeden (Adjusted Gross Score → Score Differential → tellende-vurdering).
26 scratch-sjekker grønt, pluss en egen test mot den ekte kjørende teeoff-tjenesten som bekreftet at live-oppslaget fungerer nøyaktig som tiltenkt (ingen bane-kopi skrives noe sted).
Begge migrasjoner + ny kode er nå live mot ekte teecup_db/teecup_api. teeoff.no upåvirket.
Jeg la også inn /rounds og /personal-courses i frontendens proxy-oppsett proaktivt, siden prosjektet har blitt bitt av glemte proxy-ruter før — ikke deployet ennå, tas med neste gang frontend faktisk bygges.
Notatet om slaglengde-måling og avstand-til-punkter-på-banen er lagret i CLAUDE.md og FEATURE_BACKLOG.md, med en tydelig flagget avhengighet: dette krever geografiske/GPS-data som ingen nåværende kilde (verken teeoff eller den nye banekatalogen) har.
2026-07-22 11:31:08 +02:00
|
|
|
|
""",
|
|
|
|
|
|
participant_id,
|
|
|
|
|
|
)
|
2026-07-26 08:49:37 +02:00
|
|
|
|
await broadcast_round_update(round_id)
|
|
|
|
|
|
return RoundParticipantOut(**dict(row))
|
Migrasjonen er kjørt, og jeg har fortsatt helt til et komplett, verifisert API-lag.
Underveis fant og fikset jeg et reelt hull: round_participant manglet kolonner for selve rating-tallene (kun tee-navnet var lagret) — løst med migrasjon 021, kjørt mot ekte database sammen med resten.
Bygget og rullet ut:
app/routers/rounds.py — opprett/liste/hent/slett runde, legg til/fjern gjestedeltakere, registrer statistikk hull for hull, og en fullfør-endepunkt som kjører hele kjeden (Adjusted Gross Score → Score Differential → tellende-vurdering).
26 scratch-sjekker grønt, pluss en egen test mot den ekte kjørende teeoff-tjenesten som bekreftet at live-oppslaget fungerer nøyaktig som tiltenkt (ingen bane-kopi skrives noe sted).
Begge migrasjoner + ny kode er nå live mot ekte teecup_db/teecup_api. teeoff.no upåvirket.
Jeg la også inn /rounds og /personal-courses i frontendens proxy-oppsett proaktivt, siden prosjektet har blitt bitt av glemte proxy-ruter før — ikke deployet ennå, tas med neste gang frontend faktisk bygges.
Notatet om slaglengde-måling og avstand-til-punkter-på-banen er lagret i CLAUDE.md og FEATURE_BACKLOG.md, med en tydelig flagget avhengighet: dette krever geografiske/GPS-data som ingen nåværende kilde (verken teeoff eller den nye banekatalogen) har.
2026-07-22 11:31:08 +02:00
|
|
|
|
|
|
|
|
|
|
|
2026-07-26 09:18:52 +02:00
|
|
|
|
@router.get("/rounds/{round_id}/tee-options", response_model=list[TeeOption])
|
|
|
|
|
|
async def get_round_tee_options(round_id: str, user: CurrentUser = Depends(get_current_user)) -> list[TeeOption]:
|
|
|
|
|
|
"""Utslagene tilgjengelig på RUNDENS bane -- brukt av 'rediger spiller'-
|
|
|
|
|
|
panelet til å la eieren velge et annet utslag enn rundens default for en
|
|
|
|
|
|
enkelt deltaker (f.eks. et kjønnsdelt utslag). Tilgjengelig for eier
|
|
|
|
|
|
ELLER en lenket medspiller (samme lese-tilgang som resten av runden)."""
|
|
|
|
|
|
async with plain_connection() as conn:
|
|
|
|
|
|
await _get_accessible_round_or_404(conn, round_id, user.user_id)
|
|
|
|
|
|
round_row = await conn.fetchrow(
|
|
|
|
|
|
"SELECT course_source, teeoff_facility_slug, teeoff_course_id, personal_course_id FROM round WHERE id = $1",
|
|
|
|
|
|
round_id,
|
|
|
|
|
|
)
|
|
|
|
|
|
if round_row["course_source"] == "teeoff":
|
|
|
|
|
|
resolved = await _resolve_teeoff_course(round_row["teeoff_facility_slug"], int(round_row["teeoff_course_id"]))
|
|
|
|
|
|
else:
|
|
|
|
|
|
resolved = await _resolve_personal_course(round_row["personal_course_id"])
|
|
|
|
|
|
return resolved.tee_options()
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-07-24 06:27:35 +02:00
|
|
|
|
class ParticipantUpdate(BaseModel):
|
2026-07-26 09:18:52 +02:00
|
|
|
|
# Vanlig exclude_unset-PATCH-semantikk, som resten av appen -- et
|
|
|
|
|
|
# utelatt felt endres ikke. `handicap_index` kan sendes eksplisitt som
|
|
|
|
|
|
# `null` for å fjerne HCP-sporing for denne deltakeren (samme mønster
|
|
|
|
|
|
# som profil-PATCH-en).
|
|
|
|
|
|
stat_level: StatLevel | None = None
|
|
|
|
|
|
# Utslagssted OG HCP er begge KUN et snapshot for DENNE runden (2026-07-26)
|
|
|
|
|
|
# -- endrer aldri spillerens faktiske profil, samme reproduserbarhets-
|
|
|
|
|
|
# prinsipp som resten av round_participant. Bevisst avvist etter at
|
|
|
|
|
|
# runden er fullført, samme presedens som bane-bytte i RoundUpdate
|
|
|
|
|
|
# (differensialen er da allerede beregnet fra det gamle grunnlaget).
|
|
|
|
|
|
tee_name: str | None = None
|
|
|
|
|
|
handicap_index: float | None = Field(default=None, ge=-10, le=54)
|
2026-07-26 09:42:06 +02:00
|
|
|
|
# KUN gyldig for en gjest (user_id IS NULL) -- gir ingen mening for en
|
|
|
|
|
|
# lenket bruker, som allerede har sin egen kontos navn/kjønn/e-post.
|
|
|
|
|
|
# `gender` er en del av rating-bunten under (påvirker hvilken utslags-
|
|
|
|
|
|
# rating som er gyldig), `guest_name`/`guest_email` er rene metadata.
|
|
|
|
|
|
guest_name: str | None = Field(default=None, min_length=1, max_length=100)
|
|
|
|
|
|
gender: Literal["m", "f", "x"] | None = None
|
|
|
|
|
|
guest_email: EmailStr | None = None
|
2026-07-24 06:27:35 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.patch("/rounds/{round_id}/participants/{participant_id}", response_model=RoundParticipantOut)
|
|
|
|
|
|
async def update_participant(
|
|
|
|
|
|
round_id: str,
|
|
|
|
|
|
participant_id: str,
|
|
|
|
|
|
body: ParticipantUpdate,
|
|
|
|
|
|
user: CurrentUser = Depends(get_current_user),
|
|
|
|
|
|
) -> RoundParticipantOut:
|
2026-07-26 09:18:52 +02:00
|
|
|
|
updates = body.model_dump(exclude_unset=True)
|
|
|
|
|
|
if not updates:
|
|
|
|
|
|
raise app_error(400, "VALIDATION_FAILED", "Ingen felt å oppdatere.")
|
|
|
|
|
|
|
2026-07-24 06:27:35 +02:00
|
|
|
|
async with plain_connection() as conn:
|
|
|
|
|
|
await _get_owned_round_or_404(conn, round_id, user.user_id)
|
2026-07-26 09:18:52 +02:00
|
|
|
|
current = await conn.fetchrow(
|
|
|
|
|
|
"""
|
2026-07-26 09:42:06 +02:00
|
|
|
|
SELECT rp.user_id::text AS user_id, rp.gender, rp.tee_name_snapshot,
|
2026-07-26 09:18:52 +02:00
|
|
|
|
rp.handicap_index_snapshot::float AS handicap_index_snapshot,
|
|
|
|
|
|
r.completed_at, r.course_source, r.teeoff_facility_slug,
|
|
|
|
|
|
r.teeoff_course_id, r.personal_course_id
|
|
|
|
|
|
FROM round_participant rp
|
|
|
|
|
|
JOIN round r ON r.id = rp.round_id
|
|
|
|
|
|
WHERE rp.id = $1 AND rp.round_id = $2
|
|
|
|
|
|
""",
|
|
|
|
|
|
participant_id, round_id,
|
2026-07-26 08:22:41 +02:00
|
|
|
|
)
|
2026-07-26 09:18:52 +02:00
|
|
|
|
if current is None:
|
2026-07-26 08:22:41 +02:00
|
|
|
|
raise app_error(404, "NOT_FOUND", "Deltakeren finnes ikke på denne runden.")
|
2026-07-26 09:18:52 +02:00
|
|
|
|
|
2026-07-26 09:42:06 +02:00
|
|
|
|
guest_only_fields = {"guest_name", "gender", "guest_email"}
|
|
|
|
|
|
if current["user_id"] is not None and guest_only_fields & updates.keys():
|
|
|
|
|
|
raise app_error(
|
|
|
|
|
|
400, "VALIDATION_FAILED",
|
|
|
|
|
|
"Navn/kjønn/e-post kan ikke endres her for en spiller med egen konto -- kun for en gjest.",
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
rating_changed = "tee_name" in updates or "handicap_index" in updates or "gender" in updates
|
2026-07-26 09:18:52 +02:00
|
|
|
|
if rating_changed and current["completed_at"] is not None:
|
|
|
|
|
|
raise app_error(
|
|
|
|
|
|
409, "ALREADY_COMPLETED",
|
2026-07-26 09:42:06 +02:00
|
|
|
|
"Runden er allerede fullført -- kan ikke endre utslag, HCP eller kjønn i etterkant.",
|
2026-07-26 09:18:52 +02:00
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
set_clauses: list[str] = []
|
|
|
|
|
|
values: list[object] = []
|
|
|
|
|
|
|
|
|
|
|
|
if "stat_level" in updates:
|
|
|
|
|
|
values.append(updates["stat_level"])
|
|
|
|
|
|
set_clauses.append(f"stat_level = ${len(values)}")
|
2026-07-26 09:42:06 +02:00
|
|
|
|
if "guest_name" in updates:
|
|
|
|
|
|
values.append(updates["guest_name"].strip())
|
|
|
|
|
|
set_clauses.append(f"guest_name = ${len(values)}")
|
|
|
|
|
|
if "guest_email" in updates:
|
|
|
|
|
|
values.append(updates["guest_email"])
|
|
|
|
|
|
set_clauses.append(f"guest_email = ${len(values)}")
|
2026-07-26 09:18:52 +02:00
|
|
|
|
|
|
|
|
|
|
if rating_changed:
|
|
|
|
|
|
if current["course_source"] == "teeoff":
|
|
|
|
|
|
resolved = await _resolve_teeoff_course(
|
|
|
|
|
|
current["teeoff_facility_slug"], int(current["teeoff_course_id"])
|
|
|
|
|
|
)
|
|
|
|
|
|
else:
|
|
|
|
|
|
resolved = await _resolve_personal_course(current["personal_course_id"])
|
|
|
|
|
|
|
|
|
|
|
|
new_tee_name = updates.get("tee_name") or current["tee_name_snapshot"]
|
2026-07-26 09:42:06 +02:00
|
|
|
|
new_gender = updates.get("gender") or current["gender"]
|
2026-07-26 09:18:52 +02:00
|
|
|
|
new_handicap_index = (
|
|
|
|
|
|
updates["handicap_index"] if "handicap_index" in updates else current["handicap_index_snapshot"]
|
|
|
|
|
|
)
|
2026-07-26 09:42:06 +02:00
|
|
|
|
rating = resolved.rating_for(new_tee_name, new_gender)
|
2026-07-26 09:18:52 +02:00
|
|
|
|
if rating is None:
|
|
|
|
|
|
raise app_error(
|
|
|
|
|
|
400, "VALIDATION_FAILED",
|
2026-07-26 09:42:06 +02:00
|
|
|
|
"Valgt utslag har ingen rating for spillerens (nye) kjønn.",
|
2026-07-26 09:18:52 +02:00
|
|
|
|
)
|
|
|
|
|
|
course_rating, slope_rating, tee_par = rating
|
|
|
|
|
|
course_handicap_snapshot = (
|
|
|
|
|
|
course_handicap(new_handicap_index, slope_rating, course_rating, tee_par)
|
|
|
|
|
|
if new_handicap_index is not None else None
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
values.append(new_tee_name)
|
|
|
|
|
|
set_clauses.append(f"tee_name_snapshot = ${len(values)}")
|
|
|
|
|
|
values.append(new_handicap_index)
|
|
|
|
|
|
set_clauses.append(f"handicap_index_snapshot = ${len(values)}")
|
|
|
|
|
|
values.append(course_rating)
|
|
|
|
|
|
set_clauses.append(f"course_rating_snapshot = ${len(values)}")
|
|
|
|
|
|
values.append(slope_rating)
|
|
|
|
|
|
set_clauses.append(f"slope_rating_snapshot = ${len(values)}")
|
|
|
|
|
|
values.append(tee_par)
|
|
|
|
|
|
set_clauses.append(f"tee_par_snapshot = ${len(values)}")
|
|
|
|
|
|
values.append(course_handicap_snapshot)
|
|
|
|
|
|
set_clauses.append(f"course_handicap_snapshot = ${len(values)}")
|
2026-07-26 09:42:06 +02:00
|
|
|
|
if "gender" in updates:
|
|
|
|
|
|
values.append(new_gender)
|
|
|
|
|
|
set_clauses.append(f"gender = ${len(values)}")
|
2026-07-26 09:18:52 +02:00
|
|
|
|
|
|
|
|
|
|
values.append(participant_id)
|
|
|
|
|
|
updated_id = await conn.fetchval(
|
|
|
|
|
|
f"UPDATE round_participant SET {', '.join(set_clauses)} WHERE id = ${len(values)} RETURNING id",
|
|
|
|
|
|
*values,
|
|
|
|
|
|
)
|
2026-07-24 06:27:35 +02:00
|
|
|
|
row = await conn.fetchrow(
|
|
|
|
|
|
"""
|
2026-07-26 09:42:06 +02:00
|
|
|
|
SELECT rp.id::text AS id, rp.user_id::text AS user_id, rp.guest_name, rp.guest_email,
|
2026-07-26 08:22:41 +02:00
|
|
|
|
COALESCE(rp.guest_name, au.display_name, 'Medspiller') AS display_name,
|
2026-07-26 09:18:52 +02:00
|
|
|
|
rp.is_owner, rp.gender, rp.tee_name_snapshot,
|
2026-07-26 08:22:41 +02:00
|
|
|
|
rp.handicap_index_snapshot::float AS handicap_index_snapshot,
|
|
|
|
|
|
rp.course_handicap_snapshot, rp.counts_for_handicap,
|
|
|
|
|
|
rp.score_differential::float AS score_differential, rp.stat_level
|
|
|
|
|
|
FROM round_participant rp
|
|
|
|
|
|
LEFT JOIN app_user au ON au.id = rp.user_id
|
|
|
|
|
|
WHERE rp.id = $1
|
2026-07-24 06:27:35 +02:00
|
|
|
|
""",
|
2026-07-26 08:22:41 +02:00
|
|
|
|
updated_id,
|
2026-07-24 06:27:35 +02:00
|
|
|
|
)
|
2026-07-26 09:18:52 +02:00
|
|
|
|
await broadcast_round_update(round_id)
|
|
|
|
|
|
return RoundParticipantOut(**dict(row))
|
2026-07-24 06:27:35 +02:00
|
|
|
|
|
|
|
|
|
|
|
Migrasjonen er kjørt, og jeg har fortsatt helt til et komplett, verifisert API-lag.
Underveis fant og fikset jeg et reelt hull: round_participant manglet kolonner for selve rating-tallene (kun tee-navnet var lagret) — løst med migrasjon 021, kjørt mot ekte database sammen med resten.
Bygget og rullet ut:
app/routers/rounds.py — opprett/liste/hent/slett runde, legg til/fjern gjestedeltakere, registrer statistikk hull for hull, og en fullfør-endepunkt som kjører hele kjeden (Adjusted Gross Score → Score Differential → tellende-vurdering).
26 scratch-sjekker grønt, pluss en egen test mot den ekte kjørende teeoff-tjenesten som bekreftet at live-oppslaget fungerer nøyaktig som tiltenkt (ingen bane-kopi skrives noe sted).
Begge migrasjoner + ny kode er nå live mot ekte teecup_db/teecup_api. teeoff.no upåvirket.
Jeg la også inn /rounds og /personal-courses i frontendens proxy-oppsett proaktivt, siden prosjektet har blitt bitt av glemte proxy-ruter før — ikke deployet ennå, tas med neste gang frontend faktisk bygges.
Notatet om slaglengde-måling og avstand-til-punkter-på-banen er lagret i CLAUDE.md og FEATURE_BACKLOG.md, med en tydelig flagget avhengighet: dette krever geografiske/GPS-data som ingen nåværende kilde (verken teeoff eller den nye banekatalogen) har.
2026-07-22 11:31:08 +02:00
|
|
|
|
@router.delete("/rounds/{round_id}/participants/{participant_id}", status_code=204)
|
|
|
|
|
|
async def remove_guest_participant(
|
|
|
|
|
|
round_id: str,
|
|
|
|
|
|
participant_id: str,
|
|
|
|
|
|
user: CurrentUser = Depends(get_current_user),
|
|
|
|
|
|
) -> None:
|
|
|
|
|
|
async with plain_connection() as conn:
|
|
|
|
|
|
await _get_owned_round_or_404(conn, round_id, user.user_id)
|
|
|
|
|
|
row = await conn.fetchrow(
|
|
|
|
|
|
"SELECT is_owner FROM round_participant WHERE id = $1 AND round_id = $2",
|
|
|
|
|
|
participant_id, round_id,
|
|
|
|
|
|
)
|
|
|
|
|
|
if row is None:
|
|
|
|
|
|
raise app_error(404, "NOT_FOUND", "Deltakeren finnes ikke.")
|
|
|
|
|
|
if row["is_owner"]:
|
|
|
|
|
|
raise app_error(400, "VALIDATION_FAILED", "Kan ikke fjerne runde-eieren.")
|
|
|
|
|
|
await conn.execute("DELETE FROM round_participant WHERE id = $1", participant_id)
|
2026-07-26 08:49:37 +02:00
|
|
|
|
await broadcast_round_update(round_id)
|
Migrasjonen er kjørt, og jeg har fortsatt helt til et komplett, verifisert API-lag.
Underveis fant og fikset jeg et reelt hull: round_participant manglet kolonner for selve rating-tallene (kun tee-navnet var lagret) — løst med migrasjon 021, kjørt mot ekte database sammen med resten.
Bygget og rullet ut:
app/routers/rounds.py — opprett/liste/hent/slett runde, legg til/fjern gjestedeltakere, registrer statistikk hull for hull, og en fullfør-endepunkt som kjører hele kjeden (Adjusted Gross Score → Score Differential → tellende-vurdering).
26 scratch-sjekker grønt, pluss en egen test mot den ekte kjørende teeoff-tjenesten som bekreftet at live-oppslaget fungerer nøyaktig som tiltenkt (ingen bane-kopi skrives noe sted).
Begge migrasjoner + ny kode er nå live mot ekte teecup_db/teecup_api. teeoff.no upåvirket.
Jeg la også inn /rounds og /personal-courses i frontendens proxy-oppsett proaktivt, siden prosjektet har blitt bitt av glemte proxy-ruter før — ikke deployet ennå, tas med neste gang frontend faktisk bygges.
Notatet om slaglengde-måling og avstand-til-punkter-på-banen er lagret i CLAUDE.md og FEATURE_BACKLOG.md, med en tydelig flagget avhengighet: dette krever geografiske/GPS-data som ingen nåværende kilde (verken teeoff eller den nye banekatalogen) har.
2026-07-22 11:31:08 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
# Hull-for-hull-registrering
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
|
Update Todos
Add non-org-scoped teeoff search + personal-course detail endpoints to rounds.py
Build /rounds list page + component
Build /rounds/new creation flow (course search/create, tee, date, holes)
Build /rounds/[id] detail page (participants, hole-by-hole stats, complete)
Link new feature from dashboard
Scratch-verify backend additions + typecheck production build
Deploy live after user confirmation
Update ADR-033/FEATURE_BACKLOG/CLAUDE.md status
Frontend for standalone round tracking is built and fully verified in scratch — 22 backend contract checks plus a live end-to-end test against both course sources (custom bane and real teeoff lookup), and a clean typechecked production build (docker build --target builder) showing all new routes (/rounds, /rounds/new, /rounds/[id]).
What's new, in summary:
Backend: three additions to app/routers/rounds.py — non-org-scoped teeoff facility/course search (/rounds/official-search*), a personal-course detail endpoint exposing tee/gender info, and a GET .../holes endpoint (needed since RoundOut never carried hole-level data). Also changed the hole-PATCH endpoint to return the full updated row instead of {"ok": true}. No migration — no schema touched.
Frontend: /rounds (list), /rounds/new (course search or create, tee/date/holes-planned), /rounds/[id] (participants, hole-by-hole stat entry with GIR auto-derived, complete-round flow with HCP differential summary). Linked from the dashboard both as a header shortcut and a prominent "Egne runder" entry card.
One thing worth flagging: the hole-PATCH endpoint replaces all fields every call rather than doing a true partial update, so the frontend always merges the current hole state before sending a patch — I confirmed this contract explicitly in scratch (a naive partial PATCH silently nulls out unrelated fields).
2026-07-23 05:51:26 +02:00
|
|
|
|
class RoundHoleOut(BaseModel):
|
|
|
|
|
|
hole_number: int
|
|
|
|
|
|
par: int
|
|
|
|
|
|
stroke_index: int
|
|
|
|
|
|
played: bool
|
|
|
|
|
|
score: int | None
|
|
|
|
|
|
putts: int | None
|
|
|
|
|
|
club_off_tee: str | None
|
|
|
|
|
|
tee_shot_result: str | None
|
|
|
|
|
|
approach_result: str | None
|
|
|
|
|
|
chip_count: int | None
|
|
|
|
|
|
bunker_shot_count: int | None
|
|
|
|
|
|
penalty_strokes: int | None
|
2026-07-24 06:27:35 +02:00
|
|
|
|
first_putt_distance_bucket: str | None
|
|
|
|
|
|
anyway_strokes: int | None
|
Nytt horisontalt scorekort (/my-rounds/[id]/scorecard) — ekte HTML-tabell med faste kolonnebredder, ingen scroll i det hele tatt. Score-cellene bruker form (sirkel = under par, firkant = over par) + fylt/ufylt i stedet for farge alene, med egen symbolforklaring. Ut/Inn-blokker for 18-hulls runder, én blokk for 9-hulls. Egen dedikert side (ikke sammenslått med statistikk-siden), lenket fra både "Runde fullført"-banneret og fra statistikk-siden. Byttet ut V0s egen forenklede HCP-formel med backends allerede beregnede strokes_received, så det ikke finnes to ulike slagfordelings-implementasjoner i appen. Den gamle vertikale scorekort-tabellen i statistikk-siden er fjernet — nå bor det rå scorekortet kun ett sted.
Anywayslag-fiksen din: lagt til som en tredje flis ved siden av Bunkerslag/Straffeslag, og seksjonen omdøpt fra "Chip, bunker og straffeslag" til "Annet" — notert i .md-filene at et notatfelt trolig kommer dit senere.
Ekte typesjekket build, ingen backend-endring nødvendig, teeoff.no upåvirket.
2026-07-24 23:33:41 +02:00
|
|
|
|
# Slag mottatt på dette hullet, utledet fra course_handicap_snapshot
|
|
|
|
|
|
# (samme allokeringsalgoritme som resten av appen) -- kun til visning
|
|
|
|
|
|
# av netto-score i en oversiktstabell, aldri lagret. None hvis
|
|
|
|
|
|
# deltakeren ikke har en beregnet course handicap (f.eks. gjest uten
|
|
|
|
|
|
# HCP).
|
|
|
|
|
|
strokes_received: int | None
|
Update Todos
Add non-org-scoped teeoff search + personal-course detail endpoints to rounds.py
Build /rounds list page + component
Build /rounds/new creation flow (course search/create, tee, date, holes)
Build /rounds/[id] detail page (participants, hole-by-hole stats, complete)
Link new feature from dashboard
Scratch-verify backend additions + typecheck production build
Deploy live after user confirmation
Update ADR-033/FEATURE_BACKLOG/CLAUDE.md status
Frontend for standalone round tracking is built and fully verified in scratch — 22 backend contract checks plus a live end-to-end test against both course sources (custom bane and real teeoff lookup), and a clean typechecked production build (docker build --target builder) showing all new routes (/rounds, /rounds/new, /rounds/[id]).
What's new, in summary:
Backend: three additions to app/routers/rounds.py — non-org-scoped teeoff facility/course search (/rounds/official-search*), a personal-course detail endpoint exposing tee/gender info, and a GET .../holes endpoint (needed since RoundOut never carried hole-level data). Also changed the hole-PATCH endpoint to return the full updated row instead of {"ok": true}. No migration — no schema touched.
Frontend: /rounds (list), /rounds/new (course search or create, tee/date/holes-planned), /rounds/[id] (participants, hole-by-hole stat entry with GIR auto-derived, complete-round flow with HCP differential summary). Linked from the dashboard both as a header shortcut and a prominent "Egne runder" entry card.
One thing worth flagging: the hole-PATCH endpoint replaces all fields every call rather than doing a true partial update, so the frontend always merges the current hole state before sending a patch — I confirmed this contract explicitly in scratch (a naive partial PATCH silently nulls out unrelated fields).
2026-07-23 05:51:26 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.get(
|
|
|
|
|
|
"/rounds/{round_id}/participants/{participant_id}/holes",
|
|
|
|
|
|
response_model=list[RoundHoleOut],
|
|
|
|
|
|
)
|
|
|
|
|
|
async def list_holes(
|
|
|
|
|
|
round_id: str,
|
|
|
|
|
|
participant_id: str,
|
|
|
|
|
|
user: CurrentUser = Depends(get_current_user),
|
|
|
|
|
|
) -> list[RoundHoleOut]:
|
|
|
|
|
|
async with plain_connection() as conn:
|
2026-07-26 08:22:41 +02:00
|
|
|
|
await _get_accessible_round_or_404(conn, round_id, user.user_id)
|
Nytt horisontalt scorekort (/my-rounds/[id]/scorecard) — ekte HTML-tabell med faste kolonnebredder, ingen scroll i det hele tatt. Score-cellene bruker form (sirkel = under par, firkant = over par) + fylt/ufylt i stedet for farge alene, med egen symbolforklaring. Ut/Inn-blokker for 18-hulls runder, én blokk for 9-hulls. Egen dedikert side (ikke sammenslått med statistikk-siden), lenket fra både "Runde fullført"-banneret og fra statistikk-siden. Byttet ut V0s egen forenklede HCP-formel med backends allerede beregnede strokes_received, så det ikke finnes to ulike slagfordelings-implementasjoner i appen. Den gamle vertikale scorekort-tabellen i statistikk-siden er fjernet — nå bor det rå scorekortet kun ett sted.
Anywayslag-fiksen din: lagt til som en tredje flis ved siden av Bunkerslag/Straffeslag, og seksjonen omdøpt fra "Chip, bunker og straffeslag" til "Annet" — notert i .md-filene at et notatfelt trolig kommer dit senere.
Ekte typesjekket build, ingen backend-endring nødvendig, teeoff.no upåvirket.
2026-07-24 23:33:41 +02:00
|
|
|
|
participant_row = await conn.fetchrow(
|
|
|
|
|
|
"SELECT course_handicap_snapshot FROM round_participant WHERE id = $1 AND round_id = $2",
|
Update Todos
Add non-org-scoped teeoff search + personal-course detail endpoints to rounds.py
Build /rounds list page + component
Build /rounds/new creation flow (course search/create, tee, date, holes)
Build /rounds/[id] detail page (participants, hole-by-hole stats, complete)
Link new feature from dashboard
Scratch-verify backend additions + typecheck production build
Deploy live after user confirmation
Update ADR-033/FEATURE_BACKLOG/CLAUDE.md status
Frontend for standalone round tracking is built and fully verified in scratch — 22 backend contract checks plus a live end-to-end test against both course sources (custom bane and real teeoff lookup), and a clean typechecked production build (docker build --target builder) showing all new routes (/rounds, /rounds/new, /rounds/[id]).
What's new, in summary:
Backend: three additions to app/routers/rounds.py — non-org-scoped teeoff facility/course search (/rounds/official-search*), a personal-course detail endpoint exposing tee/gender info, and a GET .../holes endpoint (needed since RoundOut never carried hole-level data). Also changed the hole-PATCH endpoint to return the full updated row instead of {"ok": true}. No migration — no schema touched.
Frontend: /rounds (list), /rounds/new (course search or create, tee/date/holes-planned), /rounds/[id] (participants, hole-by-hole stat entry with GIR auto-derived, complete-round flow with HCP differential summary). Linked from the dashboard both as a header shortcut and a prominent "Egne runder" entry card.
One thing worth flagging: the hole-PATCH endpoint replaces all fields every call rather than doing a true partial update, so the frontend always merges the current hole state before sending a patch — I confirmed this contract explicitly in scratch (a naive partial PATCH silently nulls out unrelated fields).
2026-07-23 05:51:26 +02:00
|
|
|
|
participant_id, round_id,
|
|
|
|
|
|
)
|
Nytt horisontalt scorekort (/my-rounds/[id]/scorecard) — ekte HTML-tabell med faste kolonnebredder, ingen scroll i det hele tatt. Score-cellene bruker form (sirkel = under par, firkant = over par) + fylt/ufylt i stedet for farge alene, med egen symbolforklaring. Ut/Inn-blokker for 18-hulls runder, én blokk for 9-hulls. Egen dedikert side (ikke sammenslått med statistikk-siden), lenket fra både "Runde fullført"-banneret og fra statistikk-siden. Byttet ut V0s egen forenklede HCP-formel med backends allerede beregnede strokes_received, så det ikke finnes to ulike slagfordelings-implementasjoner i appen. Den gamle vertikale scorekort-tabellen i statistikk-siden er fjernet — nå bor det rå scorekortet kun ett sted.
Anywayslag-fiksen din: lagt til som en tredje flis ved siden av Bunkerslag/Straffeslag, og seksjonen omdøpt fra "Chip, bunker og straffeslag" til "Annet" — notert i .md-filene at et notatfelt trolig kommer dit senere.
Ekte typesjekket build, ingen backend-endring nødvendig, teeoff.no upåvirket.
2026-07-24 23:33:41 +02:00
|
|
|
|
if participant_row is None:
|
Update Todos
Add non-org-scoped teeoff search + personal-course detail endpoints to rounds.py
Build /rounds list page + component
Build /rounds/new creation flow (course search/create, tee, date, holes)
Build /rounds/[id] detail page (participants, hole-by-hole stats, complete)
Link new feature from dashboard
Scratch-verify backend additions + typecheck production build
Deploy live after user confirmation
Update ADR-033/FEATURE_BACKLOG/CLAUDE.md status
Frontend for standalone round tracking is built and fully verified in scratch — 22 backend contract checks plus a live end-to-end test against both course sources (custom bane and real teeoff lookup), and a clean typechecked production build (docker build --target builder) showing all new routes (/rounds, /rounds/new, /rounds/[id]).
What's new, in summary:
Backend: three additions to app/routers/rounds.py — non-org-scoped teeoff facility/course search (/rounds/official-search*), a personal-course detail endpoint exposing tee/gender info, and a GET .../holes endpoint (needed since RoundOut never carried hole-level data). Also changed the hole-PATCH endpoint to return the full updated row instead of {"ok": true}. No migration — no schema touched.
Frontend: /rounds (list), /rounds/new (course search or create, tee/date/holes-planned), /rounds/[id] (participants, hole-by-hole stat entry with GIR auto-derived, complete-round flow with HCP differential summary). Linked from the dashboard both as a header shortcut and a prominent "Egne runder" entry card.
One thing worth flagging: the hole-PATCH endpoint replaces all fields every call rather than doing a true partial update, so the frontend always merges the current hole state before sending a patch — I confirmed this contract explicitly in scratch (a naive partial PATCH silently nulls out unrelated fields).
2026-07-23 05:51:26 +02:00
|
|
|
|
raise app_error(404, "NOT_FOUND", "Deltakeren finnes ikke på denne runden.")
|
|
|
|
|
|
rows = await conn.fetch(
|
|
|
|
|
|
"""
|
|
|
|
|
|
SELECT hole_number, par, stroke_index, played, score, putts, club_off_tee,
|
|
|
|
|
|
tee_shot_result, approach_result, chip_count, bunker_shot_count,
|
2026-07-24 06:27:35 +02:00
|
|
|
|
penalty_strokes, first_putt_distance_bucket, anyway_strokes
|
Update Todos
Add non-org-scoped teeoff search + personal-course detail endpoints to rounds.py
Build /rounds list page + component
Build /rounds/new creation flow (course search/create, tee, date, holes)
Build /rounds/[id] detail page (participants, hole-by-hole stats, complete)
Link new feature from dashboard
Scratch-verify backend additions + typecheck production build
Deploy live after user confirmation
Update ADR-033/FEATURE_BACKLOG/CLAUDE.md status
Frontend for standalone round tracking is built and fully verified in scratch — 22 backend contract checks plus a live end-to-end test against both course sources (custom bane and real teeoff lookup), and a clean typechecked production build (docker build --target builder) showing all new routes (/rounds, /rounds/new, /rounds/[id]).
What's new, in summary:
Backend: three additions to app/routers/rounds.py — non-org-scoped teeoff facility/course search (/rounds/official-search*), a personal-course detail endpoint exposing tee/gender info, and a GET .../holes endpoint (needed since RoundOut never carried hole-level data). Also changed the hole-PATCH endpoint to return the full updated row instead of {"ok": true}. No migration — no schema touched.
Frontend: /rounds (list), /rounds/new (course search or create, tee/date/holes-planned), /rounds/[id] (participants, hole-by-hole stat entry with GIR auto-derived, complete-round flow with HCP differential summary). Linked from the dashboard both as a header shortcut and a prominent "Egne runder" entry card.
One thing worth flagging: the hole-PATCH endpoint replaces all fields every call rather than doing a true partial update, so the frontend always merges the current hole state before sending a patch — I confirmed this contract explicitly in scratch (a naive partial PATCH silently nulls out unrelated fields).
2026-07-23 05:51:26 +02:00
|
|
|
|
FROM round_hole WHERE round_participant_id = $1 ORDER BY hole_number
|
|
|
|
|
|
""",
|
|
|
|
|
|
participant_id,
|
|
|
|
|
|
)
|
Nytt horisontalt scorekort (/my-rounds/[id]/scorecard) — ekte HTML-tabell med faste kolonnebredder, ingen scroll i det hele tatt. Score-cellene bruker form (sirkel = under par, firkant = over par) + fylt/ufylt i stedet for farge alene, med egen symbolforklaring. Ut/Inn-blokker for 18-hulls runder, én blokk for 9-hulls. Egen dedikert side (ikke sammenslått med statistikk-siden), lenket fra både "Runde fullført"-banneret og fra statistikk-siden. Byttet ut V0s egen forenklede HCP-formel med backends allerede beregnede strokes_received, så det ikke finnes to ulike slagfordelings-implementasjoner i appen. Den gamle vertikale scorekort-tabellen i statistikk-siden er fjernet — nå bor det rå scorekortet kun ett sted.
Anywayslag-fiksen din: lagt til som en tredje flis ved siden av Bunkerslag/Straffeslag, og seksjonen omdøpt fra "Chip, bunker og straffeslag" til "Annet" — notert i .md-filene at et notatfelt trolig kommer dit senere.
Ekte typesjekket build, ingen backend-endring nødvendig, teeoff.no upåvirket.
2026-07-24 23:33:41 +02:00
|
|
|
|
|
|
|
|
|
|
strokes_received_by_hole: dict[int, int] | None = None
|
|
|
|
|
|
if participant_row["course_handicap_snapshot"] is not None:
|
|
|
|
|
|
allocation = allocate_strokes_by_index(
|
|
|
|
|
|
participant_row["course_handicap_snapshot"], [r["stroke_index"] for r in rows]
|
|
|
|
|
|
)
|
|
|
|
|
|
strokes_received_by_hole = {r["hole_number"]: a for r, a in zip(rows, allocation)}
|
|
|
|
|
|
|
|
|
|
|
|
return [
|
|
|
|
|
|
RoundHoleOut(
|
|
|
|
|
|
**dict(r),
|
|
|
|
|
|
strokes_received=strokes_received_by_hole[r["hole_number"]] if strokes_received_by_hole else None,
|
|
|
|
|
|
)
|
|
|
|
|
|
for r in rows
|
|
|
|
|
|
]
|
Update Todos
Add non-org-scoped teeoff search + personal-course detail endpoints to rounds.py
Build /rounds list page + component
Build /rounds/new creation flow (course search/create, tee, date, holes)
Build /rounds/[id] detail page (participants, hole-by-hole stats, complete)
Link new feature from dashboard
Scratch-verify backend additions + typecheck production build
Deploy live after user confirmation
Update ADR-033/FEATURE_BACKLOG/CLAUDE.md status
Frontend for standalone round tracking is built and fully verified in scratch — 22 backend contract checks plus a live end-to-end test against both course sources (custom bane and real teeoff lookup), and a clean typechecked production build (docker build --target builder) showing all new routes (/rounds, /rounds/new, /rounds/[id]).
What's new, in summary:
Backend: three additions to app/routers/rounds.py — non-org-scoped teeoff facility/course search (/rounds/official-search*), a personal-course detail endpoint exposing tee/gender info, and a GET .../holes endpoint (needed since RoundOut never carried hole-level data). Also changed the hole-PATCH endpoint to return the full updated row instead of {"ok": true}. No migration — no schema touched.
Frontend: /rounds (list), /rounds/new (course search or create, tee/date/holes-planned), /rounds/[id] (participants, hole-by-hole stat entry with GIR auto-derived, complete-round flow with HCP differential summary). Linked from the dashboard both as a header shortcut and a prominent "Egne runder" entry card.
One thing worth flagging: the hole-PATCH endpoint replaces all fields every call rather than doing a true partial update, so the frontend always merges the current hole state before sending a patch — I confirmed this contract explicitly in scratch (a naive partial PATCH silently nulls out unrelated fields).
2026-07-23 05:51:26 +02:00
|
|
|
|
|
|
|
|
|
|
|
Varsler (fra zip 20): in-app varslingssenter er live — bjelle med uleste-tall i dashbord-headeren, /my-notifications-side. Trigges i dag ved venneforespørsel sendt/akseptert; flere hendelser kan kobles på senere.
Rundeleaderboard: ny GET /rounds/{id}/leaderboard-backend er live (rangering, thru-tall, brutto+netto til par, håndterer 1 til 15+ deltakere). V0-prompten for selve visningen ligger i FEATURE_BACKLOG.md, klar til å limes inn i v0.app — send meg zip-en når du har den, så kobler jeg den på (foreslått rute /my-rounds/[id]/leaderboard, lenket fra rundesiden).
Begge deler scratch-verifisert (39/39 sjekker, inkl. en uavhengig kryssjekk av netto-beregningen mot handicap_engine direkte), rullet ut mot ekte teecup_db/containere, teeoff.no upåvirket.
2026-07-26 06:53:58 +02:00
|
|
|
|
class LeaderboardEntryOut(BaseModel):
|
|
|
|
|
|
participant_id: str
|
|
|
|
|
|
display_name: str
|
|
|
|
|
|
is_owner: bool
|
|
|
|
|
|
holes_played: int
|
|
|
|
|
|
total_score: int | None
|
|
|
|
|
|
score_to_par: int | None
|
|
|
|
|
|
# None når deltakeren ikke har noen beregnet course handicap (f.eks.
|
|
|
|
|
|
# gjest uten registrert HCP) -- samme begrensning som strokes_received
|
|
|
|
|
|
# i RoundHoleOut/list_holes, samme algoritme (allocate_strokes_by_index).
|
|
|
|
|
|
net_score_to_par: int | None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class LeaderboardOut(BaseModel):
|
|
|
|
|
|
holes_planned: int
|
|
|
|
|
|
completed: bool
|
|
|
|
|
|
entries: list[LeaderboardEntryOut]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.get("/rounds/{round_id}/leaderboard", response_model=LeaderboardOut)
|
|
|
|
|
|
async def get_leaderboard(round_id: str, user: CurrentUser = Depends(get_current_user)) -> LeaderboardOut:
|
|
|
|
|
|
async with plain_connection() as conn:
|
2026-07-26 08:22:41 +02:00
|
|
|
|
await _get_accessible_round_or_404(conn, round_id, user.user_id)
|
Varsler (fra zip 20): in-app varslingssenter er live — bjelle med uleste-tall i dashbord-headeren, /my-notifications-side. Trigges i dag ved venneforespørsel sendt/akseptert; flere hendelser kan kobles på senere.
Rundeleaderboard: ny GET /rounds/{id}/leaderboard-backend er live (rangering, thru-tall, brutto+netto til par, håndterer 1 til 15+ deltakere). V0-prompten for selve visningen ligger i FEATURE_BACKLOG.md, klar til å limes inn i v0.app — send meg zip-en når du har den, så kobler jeg den på (foreslått rute /my-rounds/[id]/leaderboard, lenket fra rundesiden).
Begge deler scratch-verifisert (39/39 sjekker, inkl. en uavhengig kryssjekk av netto-beregningen mot handicap_engine direkte), rullet ut mot ekte teecup_db/containere, teeoff.no upåvirket.
2026-07-26 06:53:58 +02:00
|
|
|
|
round_row = await conn.fetchrow(
|
|
|
|
|
|
"SELECT holes_planned, completed_at FROM round WHERE id = $1", round_id
|
|
|
|
|
|
)
|
|
|
|
|
|
participant_rows = await conn.fetch(
|
|
|
|
|
|
"""
|
|
|
|
|
|
SELECT rp.id::text AS id, rp.is_owner, rp.guest_name, rp.course_handicap_snapshot,
|
2026-07-26 08:22:41 +02:00
|
|
|
|
au.display_name AS linked_display_name
|
Varsler (fra zip 20): in-app varslingssenter er live — bjelle med uleste-tall i dashbord-headeren, /my-notifications-side. Trigges i dag ved venneforespørsel sendt/akseptert; flere hendelser kan kobles på senere.
Rundeleaderboard: ny GET /rounds/{id}/leaderboard-backend er live (rangering, thru-tall, brutto+netto til par, håndterer 1 til 15+ deltakere). V0-prompten for selve visningen ligger i FEATURE_BACKLOG.md, klar til å limes inn i v0.app — send meg zip-en når du har den, så kobler jeg den på (foreslått rute /my-rounds/[id]/leaderboard, lenket fra rundesiden).
Begge deler scratch-verifisert (39/39 sjekker, inkl. en uavhengig kryssjekk av netto-beregningen mot handicap_engine direkte), rullet ut mot ekte teecup_db/containere, teeoff.no upåvirket.
2026-07-26 06:53:58 +02:00
|
|
|
|
FROM round_participant rp
|
|
|
|
|
|
LEFT JOIN app_user au ON au.id = rp.user_id
|
|
|
|
|
|
WHERE rp.round_id = $1
|
|
|
|
|
|
ORDER BY rp.is_owner DESC, rp.created_at
|
|
|
|
|
|
""",
|
|
|
|
|
|
round_id,
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
entries: list[LeaderboardEntryOut] = []
|
|
|
|
|
|
for p in participant_rows:
|
|
|
|
|
|
hole_rows = await conn.fetch(
|
|
|
|
|
|
"SELECT hole_number, par, stroke_index, played, score FROM round_hole WHERE round_participant_id = $1 ORDER BY hole_number",
|
|
|
|
|
|
p["id"],
|
|
|
|
|
|
)
|
|
|
|
|
|
played_holes = [h for h in hole_rows if h["played"] and h["score"] is not None]
|
|
|
|
|
|
holes_played = len(played_holes)
|
|
|
|
|
|
total_score = sum(h["score"] for h in played_holes) if holes_played else None
|
|
|
|
|
|
total_par = sum(h["par"] for h in played_holes) if holes_played else None
|
|
|
|
|
|
score_to_par = total_score - total_par if total_score is not None else None
|
|
|
|
|
|
|
|
|
|
|
|
net_score_to_par = None
|
|
|
|
|
|
if total_score is not None and p["course_handicap_snapshot"] is not None:
|
|
|
|
|
|
allocation = allocate_strokes_by_index(
|
|
|
|
|
|
p["course_handicap_snapshot"], [h["stroke_index"] for h in hole_rows]
|
|
|
|
|
|
)
|
|
|
|
|
|
strokes_received_by_hole = {h["hole_number"]: a for h, a in zip(hole_rows, allocation)}
|
|
|
|
|
|
strokes_received_total = sum(strokes_received_by_hole[h["hole_number"]] for h in played_holes)
|
|
|
|
|
|
net_score_to_par = score_to_par - strokes_received_total
|
|
|
|
|
|
|
2026-07-26 08:22:41 +02:00
|
|
|
|
# Aldri "Deg" her -- leaderboardet kan nå ses av flere (eier
|
|
|
|
|
|
# OG lenkede medspillere, ADR-036 fase 3-utvidelsen), så et
|
|
|
|
|
|
# viewer-relativt "Deg" ville vært feil for de andre som ser på.
|
|
|
|
|
|
display_name = p["guest_name"] or p["linked_display_name"] or "Medspiller"
|
Varsler (fra zip 20): in-app varslingssenter er live — bjelle med uleste-tall i dashbord-headeren, /my-notifications-side. Trigges i dag ved venneforespørsel sendt/akseptert; flere hendelser kan kobles på senere.
Rundeleaderboard: ny GET /rounds/{id}/leaderboard-backend er live (rangering, thru-tall, brutto+netto til par, håndterer 1 til 15+ deltakere). V0-prompten for selve visningen ligger i FEATURE_BACKLOG.md, klar til å limes inn i v0.app — send meg zip-en når du har den, så kobler jeg den på (foreslått rute /my-rounds/[id]/leaderboard, lenket fra rundesiden).
Begge deler scratch-verifisert (39/39 sjekker, inkl. en uavhengig kryssjekk av netto-beregningen mot handicap_engine direkte), rullet ut mot ekte teecup_db/containere, teeoff.no upåvirket.
2026-07-26 06:53:58 +02:00
|
|
|
|
entries.append(
|
|
|
|
|
|
LeaderboardEntryOut(
|
|
|
|
|
|
participant_id=p["id"],
|
|
|
|
|
|
display_name=display_name,
|
|
|
|
|
|
is_owner=p["is_owner"],
|
|
|
|
|
|
holes_played=holes_played,
|
|
|
|
|
|
total_score=total_score,
|
|
|
|
|
|
score_to_par=score_to_par,
|
|
|
|
|
|
net_score_to_par=net_score_to_par,
|
|
|
|
|
|
)
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
entries.sort(key=lambda e: (e.score_to_par is None, e.score_to_par))
|
|
|
|
|
|
|
|
|
|
|
|
return LeaderboardOut(
|
|
|
|
|
|
holes_planned=round_row["holes_planned"],
|
|
|
|
|
|
completed=round_row["completed_at"] is not None,
|
|
|
|
|
|
entries=entries,
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
Migrasjonen er kjørt, og jeg har fortsatt helt til et komplett, verifisert API-lag.
Underveis fant og fikset jeg et reelt hull: round_participant manglet kolonner for selve rating-tallene (kun tee-navnet var lagret) — løst med migrasjon 021, kjørt mot ekte database sammen med resten.
Bygget og rullet ut:
app/routers/rounds.py — opprett/liste/hent/slett runde, legg til/fjern gjestedeltakere, registrer statistikk hull for hull, og en fullfør-endepunkt som kjører hele kjeden (Adjusted Gross Score → Score Differential → tellende-vurdering).
26 scratch-sjekker grønt, pluss en egen test mot den ekte kjørende teeoff-tjenesten som bekreftet at live-oppslaget fungerer nøyaktig som tiltenkt (ingen bane-kopi skrives noe sted).
Begge migrasjoner + ny kode er nå live mot ekte teecup_db/teecup_api. teeoff.no upåvirket.
Jeg la også inn /rounds og /personal-courses i frontendens proxy-oppsett proaktivt, siden prosjektet har blitt bitt av glemte proxy-ruter før — ikke deployet ennå, tas med neste gang frontend faktisk bygges.
Notatet om slaglengde-måling og avstand-til-punkter-på-banen er lagret i CLAUDE.md og FEATURE_BACKLOG.md, med en tydelig flagget avhengighet: dette krever geografiske/GPS-data som ingen nåværende kilde (verken teeoff eller den nye banekatalogen) har.
2026-07-22 11:31:08 +02:00
|
|
|
|
class HoleUpdate(BaseModel):
|
|
|
|
|
|
played: bool = True
|
|
|
|
|
|
score: int | None = Field(default=None, ge=1, le=20)
|
|
|
|
|
|
putts: int | None = Field(default=None, ge=0, le=10)
|
|
|
|
|
|
club_off_tee: str | None = Field(default=None, max_length=50)
|
|
|
|
|
|
tee_shot_result: Literal["fairway", "left", "right"] | None = None
|
|
|
|
|
|
approach_result: Literal["hit", "long", "short", "left", "right"] | None = None
|
|
|
|
|
|
chip_count: int | None = Field(default=None, ge=0)
|
|
|
|
|
|
bunker_shot_count: int | None = Field(default=None, ge=0)
|
|
|
|
|
|
penalty_strokes: int | None = Field(default=None, ge=0)
|
2026-07-24 06:27:35 +02:00
|
|
|
|
first_putt_distance_bucket: Literal["<1m", "<2m", "<3m", "<5m", "<8m", "8m+"] | None = None
|
|
|
|
|
|
anyway_strokes: int | None = Field(default=None, ge=0)
|
Migrasjonen er kjørt, og jeg har fortsatt helt til et komplett, verifisert API-lag.
Underveis fant og fikset jeg et reelt hull: round_participant manglet kolonner for selve rating-tallene (kun tee-navnet var lagret) — løst med migrasjon 021, kjørt mot ekte database sammen med resten.
Bygget og rullet ut:
app/routers/rounds.py — opprett/liste/hent/slett runde, legg til/fjern gjestedeltakere, registrer statistikk hull for hull, og en fullfør-endepunkt som kjører hele kjeden (Adjusted Gross Score → Score Differential → tellende-vurdering).
26 scratch-sjekker grønt, pluss en egen test mot den ekte kjørende teeoff-tjenesten som bekreftet at live-oppslaget fungerer nøyaktig som tiltenkt (ingen bane-kopi skrives noe sted).
Begge migrasjoner + ny kode er nå live mot ekte teecup_db/teecup_api. teeoff.no upåvirket.
Jeg la også inn /rounds og /personal-courses i frontendens proxy-oppsett proaktivt, siden prosjektet har blitt bitt av glemte proxy-ruter før — ikke deployet ennå, tas med neste gang frontend faktisk bygges.
Notatet om slaglengde-måling og avstand-til-punkter-på-banen er lagret i CLAUDE.md og FEATURE_BACKLOG.md, med en tydelig flagget avhengighet: dette krever geografiske/GPS-data som ingen nåværende kilde (verken teeoff eller den nye banekatalogen) har.
2026-07-22 11:31:08 +02:00
|
|
|
|
|
|
|
|
|
|
|
Update Todos
Add non-org-scoped teeoff search + personal-course detail endpoints to rounds.py
Build /rounds list page + component
Build /rounds/new creation flow (course search/create, tee, date, holes)
Build /rounds/[id] detail page (participants, hole-by-hole stats, complete)
Link new feature from dashboard
Scratch-verify backend additions + typecheck production build
Deploy live after user confirmation
Update ADR-033/FEATURE_BACKLOG/CLAUDE.md status
Frontend for standalone round tracking is built and fully verified in scratch — 22 backend contract checks plus a live end-to-end test against both course sources (custom bane and real teeoff lookup), and a clean typechecked production build (docker build --target builder) showing all new routes (/rounds, /rounds/new, /rounds/[id]).
What's new, in summary:
Backend: three additions to app/routers/rounds.py — non-org-scoped teeoff facility/course search (/rounds/official-search*), a personal-course detail endpoint exposing tee/gender info, and a GET .../holes endpoint (needed since RoundOut never carried hole-level data). Also changed the hole-PATCH endpoint to return the full updated row instead of {"ok": true}. No migration — no schema touched.
Frontend: /rounds (list), /rounds/new (course search or create, tee/date/holes-planned), /rounds/[id] (participants, hole-by-hole stat entry with GIR auto-derived, complete-round flow with HCP differential summary). Linked from the dashboard both as a header shortcut and a prominent "Egne runder" entry card.
One thing worth flagging: the hole-PATCH endpoint replaces all fields every call rather than doing a true partial update, so the frontend always merges the current hole state before sending a patch — I confirmed this contract explicitly in scratch (a naive partial PATCH silently nulls out unrelated fields).
2026-07-23 05:51:26 +02:00
|
|
|
|
@router.patch(
|
|
|
|
|
|
"/rounds/{round_id}/participants/{participant_id}/holes/{hole_number}",
|
|
|
|
|
|
response_model=RoundHoleOut,
|
|
|
|
|
|
)
|
Migrasjonen er kjørt, og jeg har fortsatt helt til et komplett, verifisert API-lag.
Underveis fant og fikset jeg et reelt hull: round_participant manglet kolonner for selve rating-tallene (kun tee-navnet var lagret) — løst med migrasjon 021, kjørt mot ekte database sammen med resten.
Bygget og rullet ut:
app/routers/rounds.py — opprett/liste/hent/slett runde, legg til/fjern gjestedeltakere, registrer statistikk hull for hull, og en fullfør-endepunkt som kjører hele kjeden (Adjusted Gross Score → Score Differential → tellende-vurdering).
26 scratch-sjekker grønt, pluss en egen test mot den ekte kjørende teeoff-tjenesten som bekreftet at live-oppslaget fungerer nøyaktig som tiltenkt (ingen bane-kopi skrives noe sted).
Begge migrasjoner + ny kode er nå live mot ekte teecup_db/teecup_api. teeoff.no upåvirket.
Jeg la også inn /rounds og /personal-courses i frontendens proxy-oppsett proaktivt, siden prosjektet har blitt bitt av glemte proxy-ruter før — ikke deployet ennå, tas med neste gang frontend faktisk bygges.
Notatet om slaglengde-måling og avstand-til-punkter-på-banen er lagret i CLAUDE.md og FEATURE_BACKLOG.md, med en tydelig flagget avhengighet: dette krever geografiske/GPS-data som ingen nåværende kilde (verken teeoff eller den nye banekatalogen) har.
2026-07-22 11:31:08 +02:00
|
|
|
|
async def update_hole(
|
|
|
|
|
|
round_id: str,
|
|
|
|
|
|
participant_id: str,
|
|
|
|
|
|
hole_number: int,
|
|
|
|
|
|
body: HoleUpdate,
|
|
|
|
|
|
user: CurrentUser = Depends(get_current_user),
|
Update Todos
Add non-org-scoped teeoff search + personal-course detail endpoints to rounds.py
Build /rounds list page + component
Build /rounds/new creation flow (course search/create, tee, date, holes)
Build /rounds/[id] detail page (participants, hole-by-hole stats, complete)
Link new feature from dashboard
Scratch-verify backend additions + typecheck production build
Deploy live after user confirmation
Update ADR-033/FEATURE_BACKLOG/CLAUDE.md status
Frontend for standalone round tracking is built and fully verified in scratch — 22 backend contract checks plus a live end-to-end test against both course sources (custom bane and real teeoff lookup), and a clean typechecked production build (docker build --target builder) showing all new routes (/rounds, /rounds/new, /rounds/[id]).
What's new, in summary:
Backend: three additions to app/routers/rounds.py — non-org-scoped teeoff facility/course search (/rounds/official-search*), a personal-course detail endpoint exposing tee/gender info, and a GET .../holes endpoint (needed since RoundOut never carried hole-level data). Also changed the hole-PATCH endpoint to return the full updated row instead of {"ok": true}. No migration — no schema touched.
Frontend: /rounds (list), /rounds/new (course search or create, tee/date/holes-planned), /rounds/[id] (participants, hole-by-hole stat entry with GIR auto-derived, complete-round flow with HCP differential summary). Linked from the dashboard both as a header shortcut and a prominent "Egne runder" entry card.
One thing worth flagging: the hole-PATCH endpoint replaces all fields every call rather than doing a true partial update, so the frontend always merges the current hole state before sending a patch — I confirmed this contract explicitly in scratch (a naive partial PATCH silently nulls out unrelated fields).
2026-07-23 05:51:26 +02:00
|
|
|
|
) -> RoundHoleOut:
|
Migrasjonen er kjørt, og jeg har fortsatt helt til et komplett, verifisert API-lag.
Underveis fant og fikset jeg et reelt hull: round_participant manglet kolonner for selve rating-tallene (kun tee-navnet var lagret) — løst med migrasjon 021, kjørt mot ekte database sammen med resten.
Bygget og rullet ut:
app/routers/rounds.py — opprett/liste/hent/slett runde, legg til/fjern gjestedeltakere, registrer statistikk hull for hull, og en fullfør-endepunkt som kjører hele kjeden (Adjusted Gross Score → Score Differential → tellende-vurdering).
26 scratch-sjekker grønt, pluss en egen test mot den ekte kjørende teeoff-tjenesten som bekreftet at live-oppslaget fungerer nøyaktig som tiltenkt (ingen bane-kopi skrives noe sted).
Begge migrasjoner + ny kode er nå live mot ekte teecup_db/teecup_api. teeoff.no upåvirket.
Jeg la også inn /rounds og /personal-courses i frontendens proxy-oppsett proaktivt, siden prosjektet har blitt bitt av glemte proxy-ruter før — ikke deployet ennå, tas med neste gang frontend faktisk bygges.
Notatet om slaglengde-måling og avstand-til-punkter-på-banen er lagret i CLAUDE.md og FEATURE_BACKLOG.md, med en tydelig flagget avhengighet: dette krever geografiske/GPS-data som ingen nåværende kilde (verken teeoff eller den nye banekatalogen) har.
2026-07-22 11:31:08 +02:00
|
|
|
|
async with plain_connection() as conn:
|
2026-07-26 08:22:41 +02:00
|
|
|
|
# ADR-036 fase 3-utvidelsen (2026-07-26): en lenket medspiller kan
|
|
|
|
|
|
# registrere score for HELE flighten (bekreftet regel), ikke bare
|
|
|
|
|
|
# sin egen rad -- derfor accessible-sjekk, ikke owner-only, og
|
|
|
|
|
|
# INGEN participant_id == viewer-sjekk under.
|
|
|
|
|
|
await _get_accessible_round_or_404(conn, round_id, user.user_id)
|
Nytt horisontalt scorekort (/my-rounds/[id]/scorecard) — ekte HTML-tabell med faste kolonnebredder, ingen scroll i det hele tatt. Score-cellene bruker form (sirkel = under par, firkant = over par) + fylt/ufylt i stedet for farge alene, med egen symbolforklaring. Ut/Inn-blokker for 18-hulls runder, én blokk for 9-hulls. Egen dedikert side (ikke sammenslått med statistikk-siden), lenket fra både "Runde fullført"-banneret og fra statistikk-siden. Byttet ut V0s egen forenklede HCP-formel med backends allerede beregnede strokes_received, så det ikke finnes to ulike slagfordelings-implementasjoner i appen. Den gamle vertikale scorekort-tabellen i statistikk-siden er fjernet — nå bor det rå scorekortet kun ett sted.
Anywayslag-fiksen din: lagt til som en tredje flis ved siden av Bunkerslag/Straffeslag, og seksjonen omdøpt fra "Chip, bunker og straffeslag" til "Annet" — notert i .md-filene at et notatfelt trolig kommer dit senere.
Ekte typesjekket build, ingen backend-endring nødvendig, teeoff.no upåvirket.
2026-07-24 23:33:41 +02:00
|
|
|
|
participant_row = await conn.fetchrow(
|
|
|
|
|
|
"SELECT course_handicap_snapshot FROM round_participant WHERE id = $1 AND round_id = $2",
|
Migrasjonen er kjørt, og jeg har fortsatt helt til et komplett, verifisert API-lag.
Underveis fant og fikset jeg et reelt hull: round_participant manglet kolonner for selve rating-tallene (kun tee-navnet var lagret) — løst med migrasjon 021, kjørt mot ekte database sammen med resten.
Bygget og rullet ut:
app/routers/rounds.py — opprett/liste/hent/slett runde, legg til/fjern gjestedeltakere, registrer statistikk hull for hull, og en fullfør-endepunkt som kjører hele kjeden (Adjusted Gross Score → Score Differential → tellende-vurdering).
26 scratch-sjekker grønt, pluss en egen test mot den ekte kjørende teeoff-tjenesten som bekreftet at live-oppslaget fungerer nøyaktig som tiltenkt (ingen bane-kopi skrives noe sted).
Begge migrasjoner + ny kode er nå live mot ekte teecup_db/teecup_api. teeoff.no upåvirket.
Jeg la også inn /rounds og /personal-courses i frontendens proxy-oppsett proaktivt, siden prosjektet har blitt bitt av glemte proxy-ruter før — ikke deployet ennå, tas med neste gang frontend faktisk bygges.
Notatet om slaglengde-måling og avstand-til-punkter-på-banen er lagret i CLAUDE.md og FEATURE_BACKLOG.md, med en tydelig flagget avhengighet: dette krever geografiske/GPS-data som ingen nåværende kilde (verken teeoff eller den nye banekatalogen) har.
2026-07-22 11:31:08 +02:00
|
|
|
|
participant_id, round_id,
|
|
|
|
|
|
)
|
Nytt horisontalt scorekort (/my-rounds/[id]/scorecard) — ekte HTML-tabell med faste kolonnebredder, ingen scroll i det hele tatt. Score-cellene bruker form (sirkel = under par, firkant = over par) + fylt/ufylt i stedet for farge alene, med egen symbolforklaring. Ut/Inn-blokker for 18-hulls runder, én blokk for 9-hulls. Egen dedikert side (ikke sammenslått med statistikk-siden), lenket fra både "Runde fullført"-banneret og fra statistikk-siden. Byttet ut V0s egen forenklede HCP-formel med backends allerede beregnede strokes_received, så det ikke finnes to ulike slagfordelings-implementasjoner i appen. Den gamle vertikale scorekort-tabellen i statistikk-siden er fjernet — nå bor det rå scorekortet kun ett sted.
Anywayslag-fiksen din: lagt til som en tredje flis ved siden av Bunkerslag/Straffeslag, og seksjonen omdøpt fra "Chip, bunker og straffeslag" til "Annet" — notert i .md-filene at et notatfelt trolig kommer dit senere.
Ekte typesjekket build, ingen backend-endring nødvendig, teeoff.no upåvirket.
2026-07-24 23:33:41 +02:00
|
|
|
|
if participant_row is None:
|
Migrasjonen er kjørt, og jeg har fortsatt helt til et komplett, verifisert API-lag.
Underveis fant og fikset jeg et reelt hull: round_participant manglet kolonner for selve rating-tallene (kun tee-navnet var lagret) — løst med migrasjon 021, kjørt mot ekte database sammen med resten.
Bygget og rullet ut:
app/routers/rounds.py — opprett/liste/hent/slett runde, legg til/fjern gjestedeltakere, registrer statistikk hull for hull, og en fullfør-endepunkt som kjører hele kjeden (Adjusted Gross Score → Score Differential → tellende-vurdering).
26 scratch-sjekker grønt, pluss en egen test mot den ekte kjørende teeoff-tjenesten som bekreftet at live-oppslaget fungerer nøyaktig som tiltenkt (ingen bane-kopi skrives noe sted).
Begge migrasjoner + ny kode er nå live mot ekte teecup_db/teecup_api. teeoff.no upåvirket.
Jeg la også inn /rounds og /personal-courses i frontendens proxy-oppsett proaktivt, siden prosjektet har blitt bitt av glemte proxy-ruter før — ikke deployet ennå, tas med neste gang frontend faktisk bygges.
Notatet om slaglengde-måling og avstand-til-punkter-på-banen er lagret i CLAUDE.md og FEATURE_BACKLOG.md, med en tydelig flagget avhengighet: dette krever geografiske/GPS-data som ingen nåværende kilde (verken teeoff eller den nye banekatalogen) har.
2026-07-22 11:31:08 +02:00
|
|
|
|
raise app_error(404, "NOT_FOUND", "Deltakeren finnes ikke på denne runden.")
|
|
|
|
|
|
|
|
|
|
|
|
async with translate_db_errors():
|
|
|
|
|
|
row = await conn.fetchrow(
|
|
|
|
|
|
"""
|
|
|
|
|
|
UPDATE round_hole SET
|
|
|
|
|
|
played = $3, score = $4, putts = $5, club_off_tee = $6,
|
|
|
|
|
|
tee_shot_result = $7, approach_result = $8, chip_count = $9,
|
2026-07-24 06:27:35 +02:00
|
|
|
|
bunker_shot_count = $10, penalty_strokes = $11, first_putt_distance_bucket = $12,
|
|
|
|
|
|
anyway_strokes = $13
|
Migrasjonen er kjørt, og jeg har fortsatt helt til et komplett, verifisert API-lag.
Underveis fant og fikset jeg et reelt hull: round_participant manglet kolonner for selve rating-tallene (kun tee-navnet var lagret) — løst med migrasjon 021, kjørt mot ekte database sammen med resten.
Bygget og rullet ut:
app/routers/rounds.py — opprett/liste/hent/slett runde, legg til/fjern gjestedeltakere, registrer statistikk hull for hull, og en fullfør-endepunkt som kjører hele kjeden (Adjusted Gross Score → Score Differential → tellende-vurdering).
26 scratch-sjekker grønt, pluss en egen test mot den ekte kjørende teeoff-tjenesten som bekreftet at live-oppslaget fungerer nøyaktig som tiltenkt (ingen bane-kopi skrives noe sted).
Begge migrasjoner + ny kode er nå live mot ekte teecup_db/teecup_api. teeoff.no upåvirket.
Jeg la også inn /rounds og /personal-courses i frontendens proxy-oppsett proaktivt, siden prosjektet har blitt bitt av glemte proxy-ruter før — ikke deployet ennå, tas med neste gang frontend faktisk bygges.
Notatet om slaglengde-måling og avstand-til-punkter-på-banen er lagret i CLAUDE.md og FEATURE_BACKLOG.md, med en tydelig flagget avhengighet: dette krever geografiske/GPS-data som ingen nåværende kilde (verken teeoff eller den nye banekatalogen) har.
2026-07-22 11:31:08 +02:00
|
|
|
|
WHERE round_participant_id = $1 AND hole_number = $2
|
Update Todos
Add non-org-scoped teeoff search + personal-course detail endpoints to rounds.py
Build /rounds list page + component
Build /rounds/new creation flow (course search/create, tee, date, holes)
Build /rounds/[id] detail page (participants, hole-by-hole stats, complete)
Link new feature from dashboard
Scratch-verify backend additions + typecheck production build
Deploy live after user confirmation
Update ADR-033/FEATURE_BACKLOG/CLAUDE.md status
Frontend for standalone round tracking is built and fully verified in scratch — 22 backend contract checks plus a live end-to-end test against both course sources (custom bane and real teeoff lookup), and a clean typechecked production build (docker build --target builder) showing all new routes (/rounds, /rounds/new, /rounds/[id]).
What's new, in summary:
Backend: three additions to app/routers/rounds.py — non-org-scoped teeoff facility/course search (/rounds/official-search*), a personal-course detail endpoint exposing tee/gender info, and a GET .../holes endpoint (needed since RoundOut never carried hole-level data). Also changed the hole-PATCH endpoint to return the full updated row instead of {"ok": true}. No migration — no schema touched.
Frontend: /rounds (list), /rounds/new (course search or create, tee/date/holes-planned), /rounds/[id] (participants, hole-by-hole stat entry with GIR auto-derived, complete-round flow with HCP differential summary). Linked from the dashboard both as a header shortcut and a prominent "Egne runder" entry card.
One thing worth flagging: the hole-PATCH endpoint replaces all fields every call rather than doing a true partial update, so the frontend always merges the current hole state before sending a patch — I confirmed this contract explicitly in scratch (a naive partial PATCH silently nulls out unrelated fields).
2026-07-23 05:51:26 +02:00
|
|
|
|
RETURNING hole_number, par, stroke_index, played, score, putts, club_off_tee,
|
|
|
|
|
|
tee_shot_result, approach_result, chip_count, bunker_shot_count,
|
2026-07-24 06:27:35 +02:00
|
|
|
|
penalty_strokes, first_putt_distance_bucket, anyway_strokes
|
Migrasjonen er kjørt, og jeg har fortsatt helt til et komplett, verifisert API-lag.
Underveis fant og fikset jeg et reelt hull: round_participant manglet kolonner for selve rating-tallene (kun tee-navnet var lagret) — løst med migrasjon 021, kjørt mot ekte database sammen med resten.
Bygget og rullet ut:
app/routers/rounds.py — opprett/liste/hent/slett runde, legg til/fjern gjestedeltakere, registrer statistikk hull for hull, og en fullfør-endepunkt som kjører hele kjeden (Adjusted Gross Score → Score Differential → tellende-vurdering).
26 scratch-sjekker grønt, pluss en egen test mot den ekte kjørende teeoff-tjenesten som bekreftet at live-oppslaget fungerer nøyaktig som tiltenkt (ingen bane-kopi skrives noe sted).
Begge migrasjoner + ny kode er nå live mot ekte teecup_db/teecup_api. teeoff.no upåvirket.
Jeg la også inn /rounds og /personal-courses i frontendens proxy-oppsett proaktivt, siden prosjektet har blitt bitt av glemte proxy-ruter før — ikke deployet ennå, tas med neste gang frontend faktisk bygges.
Notatet om slaglengde-måling og avstand-til-punkter-på-banen er lagret i CLAUDE.md og FEATURE_BACKLOG.md, med en tydelig flagget avhengighet: dette krever geografiske/GPS-data som ingen nåværende kilde (verken teeoff eller den nye banekatalogen) har.
2026-07-22 11:31:08 +02:00
|
|
|
|
""",
|
|
|
|
|
|
participant_id, hole_number,
|
|
|
|
|
|
body.played, body.score, body.putts, body.club_off_tee,
|
|
|
|
|
|
body.tee_shot_result, body.approach_result, body.chip_count,
|
2026-07-24 06:27:35 +02:00
|
|
|
|
body.bunker_shot_count, body.penalty_strokes, body.first_putt_distance_bucket,
|
|
|
|
|
|
body.anyway_strokes,
|
Migrasjonen er kjørt, og jeg har fortsatt helt til et komplett, verifisert API-lag.
Underveis fant og fikset jeg et reelt hull: round_participant manglet kolonner for selve rating-tallene (kun tee-navnet var lagret) — løst med migrasjon 021, kjørt mot ekte database sammen med resten.
Bygget og rullet ut:
app/routers/rounds.py — opprett/liste/hent/slett runde, legg til/fjern gjestedeltakere, registrer statistikk hull for hull, og en fullfør-endepunkt som kjører hele kjeden (Adjusted Gross Score → Score Differential → tellende-vurdering).
26 scratch-sjekker grønt, pluss en egen test mot den ekte kjørende teeoff-tjenesten som bekreftet at live-oppslaget fungerer nøyaktig som tiltenkt (ingen bane-kopi skrives noe sted).
Begge migrasjoner + ny kode er nå live mot ekte teecup_db/teecup_api. teeoff.no upåvirket.
Jeg la også inn /rounds og /personal-courses i frontendens proxy-oppsett proaktivt, siden prosjektet har blitt bitt av glemte proxy-ruter før — ikke deployet ennå, tas med neste gang frontend faktisk bygges.
Notatet om slaglengde-måling og avstand-til-punkter-på-banen er lagret i CLAUDE.md og FEATURE_BACKLOG.md, med en tydelig flagget avhengighet: dette krever geografiske/GPS-data som ingen nåværende kilde (verken teeoff eller den nye banekatalogen) har.
2026-07-22 11:31:08 +02:00
|
|
|
|
)
|
|
|
|
|
|
if row is None:
|
|
|
|
|
|
raise app_error(404, "NOT_FOUND", "Hullet finnes ikke på denne deltakeren.")
|
Nytt horisontalt scorekort (/my-rounds/[id]/scorecard) — ekte HTML-tabell med faste kolonnebredder, ingen scroll i det hele tatt. Score-cellene bruker form (sirkel = under par, firkant = over par) + fylt/ufylt i stedet for farge alene, med egen symbolforklaring. Ut/Inn-blokker for 18-hulls runder, én blokk for 9-hulls. Egen dedikert side (ikke sammenslått med statistikk-siden), lenket fra både "Runde fullført"-banneret og fra statistikk-siden. Byttet ut V0s egen forenklede HCP-formel med backends allerede beregnede strokes_received, så det ikke finnes to ulike slagfordelings-implementasjoner i appen. Den gamle vertikale scorekort-tabellen i statistikk-siden er fjernet — nå bor det rå scorekortet kun ett sted.
Anywayslag-fiksen din: lagt til som en tredje flis ved siden av Bunkerslag/Straffeslag, og seksjonen omdøpt fra "Chip, bunker og straffeslag" til "Annet" — notert i .md-filene at et notatfelt trolig kommer dit senere.
Ekte typesjekket build, ingen backend-endring nødvendig, teeoff.no upåvirket.
2026-07-24 23:33:41 +02:00
|
|
|
|
|
|
|
|
|
|
# strokes_received er utledet av HELE rundens stroke-indeks-rekkefølge
|
|
|
|
|
|
# (samme allokeringsalgoritme som list_holes/GET), ikke bare dette ene
|
|
|
|
|
|
# hullet -- må derfor hente alle 18 sin stroke_index for å plassere
|
|
|
|
|
|
# riktig antall mottatte slag på nøyaktig dette hullet.
|
|
|
|
|
|
strokes_received = None
|
|
|
|
|
|
if participant_row["course_handicap_snapshot"] is not None:
|
|
|
|
|
|
all_indexes = await conn.fetch(
|
|
|
|
|
|
"SELECT hole_number, stroke_index FROM round_hole WHERE round_participant_id = $1 ORDER BY hole_number",
|
|
|
|
|
|
participant_id,
|
|
|
|
|
|
)
|
|
|
|
|
|
allocation = allocate_strokes_by_index(
|
|
|
|
|
|
participant_row["course_handicap_snapshot"], [r["stroke_index"] for r in all_indexes]
|
|
|
|
|
|
)
|
|
|
|
|
|
by_hole = {r["hole_number"]: a for r, a in zip(all_indexes, allocation)}
|
|
|
|
|
|
strokes_received = by_hole[hole_number]
|
|
|
|
|
|
|
2026-07-26 08:49:37 +02:00
|
|
|
|
await broadcast_round_update(round_id)
|
Nytt horisontalt scorekort (/my-rounds/[id]/scorecard) — ekte HTML-tabell med faste kolonnebredder, ingen scroll i det hele tatt. Score-cellene bruker form (sirkel = under par, firkant = over par) + fylt/ufylt i stedet for farge alene, med egen symbolforklaring. Ut/Inn-blokker for 18-hulls runder, én blokk for 9-hulls. Egen dedikert side (ikke sammenslått med statistikk-siden), lenket fra både "Runde fullført"-banneret og fra statistikk-siden. Byttet ut V0s egen forenklede HCP-formel med backends allerede beregnede strokes_received, så det ikke finnes to ulike slagfordelings-implementasjoner i appen. Den gamle vertikale scorekort-tabellen i statistikk-siden er fjernet — nå bor det rå scorekortet kun ett sted.
Anywayslag-fiksen din: lagt til som en tredje flis ved siden av Bunkerslag/Straffeslag, og seksjonen omdøpt fra "Chip, bunker og straffeslag" til "Annet" — notert i .md-filene at et notatfelt trolig kommer dit senere.
Ekte typesjekket build, ingen backend-endring nødvendig, teeoff.no upåvirket.
2026-07-24 23:33:41 +02:00
|
|
|
|
return RoundHoleOut(**dict(row), strokes_received=strokes_received)
|
Migrasjonen er kjørt, og jeg har fortsatt helt til et komplett, verifisert API-lag.
Underveis fant og fikset jeg et reelt hull: round_participant manglet kolonner for selve rating-tallene (kun tee-navnet var lagret) — løst med migrasjon 021, kjørt mot ekte database sammen med resten.
Bygget og rullet ut:
app/routers/rounds.py — opprett/liste/hent/slett runde, legg til/fjern gjestedeltakere, registrer statistikk hull for hull, og en fullfør-endepunkt som kjører hele kjeden (Adjusted Gross Score → Score Differential → tellende-vurdering).
26 scratch-sjekker grønt, pluss en egen test mot den ekte kjørende teeoff-tjenesten som bekreftet at live-oppslaget fungerer nøyaktig som tiltenkt (ingen bane-kopi skrives noe sted).
Begge migrasjoner + ny kode er nå live mot ekte teecup_db/teecup_api. teeoff.no upåvirket.
Jeg la også inn /rounds og /personal-courses i frontendens proxy-oppsett proaktivt, siden prosjektet har blitt bitt av glemte proxy-ruter før — ikke deployet ennå, tas med neste gang frontend faktisk bygges.
Notatet om slaglengde-måling og avstand-til-punkter-på-banen er lagret i CLAUDE.md og FEATURE_BACKLOG.md, med en tydelig flagget avhengighet: dette krever geografiske/GPS-data som ingen nåværende kilde (verken teeoff eller den nye banekatalogen) har.
2026-07-22 11:31:08 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
# Fullføring -- Adjusted Gross Score / Score Differential (ADR-033 Beslutning G)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
|
|
@router.post("/rounds/{round_id}/complete", response_model=RoundOut)
|
|
|
|
|
|
async def complete_round(round_id: str, user: CurrentUser = Depends(get_current_user)) -> RoundOut:
|
|
|
|
|
|
async with plain_connection() as conn:
|
2026-07-26 08:22:41 +02:00
|
|
|
|
# Samme begrunnelse som update_hole -- en medspiller som fører
|
|
|
|
|
|
# score for flighten skal også kunne markere runden ferdig.
|
|
|
|
|
|
await _get_accessible_round_or_404(conn, round_id, user.user_id)
|
Migrasjonen er kjørt, og jeg har fortsatt helt til et komplett, verifisert API-lag.
Underveis fant og fikset jeg et reelt hull: round_participant manglet kolonner for selve rating-tallene (kun tee-navnet var lagret) — løst med migrasjon 021, kjørt mot ekte database sammen med resten.
Bygget og rullet ut:
app/routers/rounds.py — opprett/liste/hent/slett runde, legg til/fjern gjestedeltakere, registrer statistikk hull for hull, og en fullfør-endepunkt som kjører hele kjeden (Adjusted Gross Score → Score Differential → tellende-vurdering).
26 scratch-sjekker grønt, pluss en egen test mot den ekte kjørende teeoff-tjenesten som bekreftet at live-oppslaget fungerer nøyaktig som tiltenkt (ingen bane-kopi skrives noe sted).
Begge migrasjoner + ny kode er nå live mot ekte teecup_db/teecup_api. teeoff.no upåvirket.
Jeg la også inn /rounds og /personal-courses i frontendens proxy-oppsett proaktivt, siden prosjektet har blitt bitt av glemte proxy-ruter før — ikke deployet ennå, tas med neste gang frontend faktisk bygges.
Notatet om slaglengde-måling og avstand-til-punkter-på-banen er lagret i CLAUDE.md og FEATURE_BACKLOG.md, med en tydelig flagget avhengighet: dette krever geografiske/GPS-data som ingen nåværende kilde (verken teeoff eller den nye banekatalogen) har.
2026-07-22 11:31:08 +02:00
|
|
|
|
round_row = await conn.fetchrow("SELECT holes_planned FROM round WHERE id = $1", round_id)
|
|
|
|
|
|
|
|
|
|
|
|
participants = await conn.fetch(
|
|
|
|
|
|
"""
|
|
|
|
|
|
SELECT id::text AS id, handicap_index_snapshot::float AS handicap_index_snapshot,
|
|
|
|
|
|
course_rating_snapshot::float AS course_rating_snapshot,
|
|
|
|
|
|
slope_rating_snapshot, tee_par_snapshot
|
|
|
|
|
|
FROM round_participant WHERE round_id = $1
|
|
|
|
|
|
""",
|
|
|
|
|
|
round_id,
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
for p in participants:
|
|
|
|
|
|
holes = await conn.fetch(
|
|
|
|
|
|
"""
|
|
|
|
|
|
SELECT hole_number, par, stroke_index, played, score
|
|
|
|
|
|
FROM round_hole WHERE round_participant_id = $1 ORDER BY hole_number
|
|
|
|
|
|
""",
|
|
|
|
|
|
p["id"],
|
|
|
|
|
|
)
|
|
|
|
|
|
played_count = sum(1 for h in holes if h["played"])
|
|
|
|
|
|
counts = round_counts_for_handicap(played_count, round_row["holes_planned"])
|
|
|
|
|
|
differential = None
|
|
|
|
|
|
|
|
|
|
|
|
if counts and p["handicap_index_snapshot"] is not None and p["course_rating_snapshot"] is not None:
|
|
|
|
|
|
pars = [h["par"] for h in holes]
|
|
|
|
|
|
strokes_received = allocate_strokes_by_index(
|
|
|
|
|
|
_course_handicap_from_row(p),
|
|
|
|
|
|
[h["stroke_index"] for h in holes],
|
|
|
|
|
|
)
|
|
|
|
|
|
scores = [h["score"] if h["played"] else None for h in holes]
|
|
|
|
|
|
ags = adjusted_gross_score(scores, pars, strokes_received)
|
|
|
|
|
|
differential = score_differential(ags, p["course_rating_snapshot"], p["slope_rating_snapshot"])
|
|
|
|
|
|
|
|
|
|
|
|
await conn.execute(
|
|
|
|
|
|
"UPDATE round_participant SET counts_for_handicap = $2, score_differential = $3 WHERE id = $1",
|
|
|
|
|
|
p["id"], counts, differential,
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
await conn.execute("UPDATE round SET completed_at = now() WHERE id = $1", round_id)
|
2026-07-26 08:49:37 +02:00
|
|
|
|
result = await _load_round_out(conn, round_id, user.user_id)
|
|
|
|
|
|
|
|
|
|
|
|
await broadcast_round_update(round_id)
|
|
|
|
|
|
return result
|
Migrasjonen er kjørt, og jeg har fortsatt helt til et komplett, verifisert API-lag.
Underveis fant og fikset jeg et reelt hull: round_participant manglet kolonner for selve rating-tallene (kun tee-navnet var lagret) — løst med migrasjon 021, kjørt mot ekte database sammen med resten.
Bygget og rullet ut:
app/routers/rounds.py — opprett/liste/hent/slett runde, legg til/fjern gjestedeltakere, registrer statistikk hull for hull, og en fullfør-endepunkt som kjører hele kjeden (Adjusted Gross Score → Score Differential → tellende-vurdering).
26 scratch-sjekker grønt, pluss en egen test mot den ekte kjørende teeoff-tjenesten som bekreftet at live-oppslaget fungerer nøyaktig som tiltenkt (ingen bane-kopi skrives noe sted).
Begge migrasjoner + ny kode er nå live mot ekte teecup_db/teecup_api. teeoff.no upåvirket.
Jeg la også inn /rounds og /personal-courses i frontendens proxy-oppsett proaktivt, siden prosjektet har blitt bitt av glemte proxy-ruter før — ikke deployet ennå, tas med neste gang frontend faktisk bygges.
Notatet om slaglengde-måling og avstand-til-punkter-på-banen er lagret i CLAUDE.md og FEATURE_BACKLOG.md, med en tydelig flagget avhengighet: dette krever geografiske/GPS-data som ingen nåværende kilde (verken teeoff eller den nye banekatalogen) har.
2026-07-22 11:31:08 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _course_handicap_from_row(p) -> int:
|
|
|
|
|
|
return course_handicap(
|
|
|
|
|
|
p["handicap_index_snapshot"], p["slope_rating_snapshot"], p["course_rating_snapshot"], p["tee_par_snapshot"]
|
|
|
|
|
|
)
|
2026-07-26 08:49:37 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
# Sanntid (2026-07-26, oppfølging av ADR-027) -- "noe endret seg, hent på
|
|
|
|
|
|
# nytt"-signal for round-detail.tsx/round-stats.tsx/round-scorecard.tsx.
|
|
|
|
|
|
# Ulikt turnering-live (ADR-027) er en runde ALDRI anonymt tilgjengelig --
|
|
|
|
|
|
# krever alltid en ekte, autorisert sesjon (eier ELLER lenket medspiller).
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
|
|
@router.websocket("/ws/rounds/{round_id}/live")
|
|
|
|
|
|
async def round_live_ws(websocket: WebSocket, round_id: str) -> None:
|
|
|
|
|
|
user = await get_current_user_from_websocket(websocket)
|
|
|
|
|
|
if user is None:
|
|
|
|
|
|
await websocket.close(code=4401)
|
|
|
|
|
|
return
|
|
|
|
|
|
async with plain_connection() as conn:
|
|
|
|
|
|
try:
|
|
|
|
|
|
await _get_accessible_round_or_404(conn, round_id, user.user_id)
|
|
|
|
|
|
except HTTPException as exc:
|
|
|
|
|
|
await websocket.close(code=4404 if exc.status_code == 404 else 4403)
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
await websocket.accept()
|
|
|
|
|
|
sockets = live_sockets_for_round(round_id)
|
|
|
|
|
|
sockets.add(websocket)
|
|
|
|
|
|
try:
|
|
|
|
|
|
while True:
|
|
|
|
|
|
await websocket.receive_text()
|
|
|
|
|
|
except WebSocketDisconnect:
|
|
|
|
|
|
pass
|
|
|
|
|
|
finally:
|
|
|
|
|
|
sockets.discard(websocket)
|