feat: target rating calculator (required round average for N rounds) #2

Closed
opened 2026-05-21 11:00:16 +02:00 by shcizo · 0 comments
Owner

Beskrivning

Lägg till en funktion där användaren anger en önskad rating och antal rundor (t.ex. 930 på 4 rundor), och systemet räknar ut vilket snitt spelaren behöver gå på dessa rundor för att hamna på targetratingen efter nästa officiella PDGA-uträkning.

Detta är en naturlig förlängning av befintlig "Predicted rating"-funktion: istället för att förutsäga resultatet av nuvarande historik, inverterar vi formeln för att hitta vilka framtida rundor som krävs för ett önskat resultat.

Design

Placering: Ny ikon/knapp i views/partials/ratings-table.ejs bredvid Predicted-kolumnen (samma rad som showDebugInfo-ikonen). Knappen öppnar en modal — återanvänd modal-mönstret som redan finns i public/js/players.js för debug-info.

Modal-innehåll:

  • Input 1: Target rating (number, t.ex. 930)
  • Input 2: Antal rundor (number, 1–20)
  • Knapp: "Räkna ut"
  • Resultatpanel som visar:
    • Snittkrav: XXX rating (det enda krävda värdet)
    • Liten förklaringstext: "För att gå från <current> till <target><N> rundor behöver du snitta XXX på dessa rundor (beräknat enligt PDGA:s officiella uträkning för nästa publicering)."
    • Varning om orealistiskt: Om XXX > 1050 (PDGA top-100-nivå) eller XXX < 600, visa varning: "⚠️ Detta är ett extremt värde — kontrollera att din target är rimlig."

Layout: Återanvänd .card-section + .modal CSS från befintliga modaler.

Tekniska noteringar

Backend

Ny endpoint: POST /api/calculate-target-rating/:pdgaNumber i src/routes/players.js

Request body: { targetRating: number, rounds: number }

Response: { requiredAverage: number, currentRating: number, predictedRating: number, warning?: string }

Ny funktion i src/services/rating-calculator.js:

calculateRequiredAverage(roundRatings, targetRating, numRounds)  { requiredAverage, warning? }

Implementation: binärsökning över kandidatratings R i intervallet [400, 1200]. För varje R:

  1. Bygg simulatedRounds = roundRatings ∪ [{ rating: R, date: <just före next PDGA-update>, competition: 'simulated' } × numRounds]
  2. Kör befintliga calculatePredictedRating(simulatedRounds) — den hanterar redan PDGA-cutoff, outlier-borttagning och dubbelviktning korrekt
  3. Jämför returnerat rating mot targetRating
  4. Konvergera tills |predicted - target| < 0.5

Datum för simulerade rundor sätts till getNextPDGAUpdateDate() - 1 dag så de räknas in i nästa publicering.

Varför binärsökning: Outlier-removal (≥7 rundor) och dubbelviktning (≥9 rundor) gör formeln icke-linjär i R. Binärsökning är ~20 iterationer av en cheap funktion — försumbar kostnad jämfört med en closed-form-lösning som måste specialfalla varje gren.

Edge cases att hantera:

  • Spelare utan round_history: returnera fel "ingen historik tillgänglig — refresha rundor först"
  • Target identiskt med predicted: requiredAverage = predicted (trivialt)
  • Binärsökningen konvergerar inte (target är ej nåbart inom [400, 1200]): returnera närmaste gräns + warning

Frontend

Ny fil: lägg till handler i public/js/players.js (eller bryt ut till ny public/js/target-rating.js om det blir >100 rader):

  • openTargetRatingModal(pdgaNumber) — öppnar modalen, läser nuvarande rating
  • calculateTargetRating(pdgaNumber)fetch till nya endpointen, renderar resultat
  • Validering: rounds är heltal 1–20, target är 400–1200

Mall: Lägg modal-markup i views/partials/target-rating-modal.ejs (ny partial), include:a från views/pages/index.ejs.

HTMX vs vanilla fetch: Befintlig modal-logik (showDebugInfo) använder vanilla fetch + DOM-manipulation. Följ samma mönster för konsistens.

Databas

Inga schema-ändringar behövs — beräkningen är stateless, vi cachar inte target-resultat (de är cheap att räkna om vid varje input-ändring).

Scope

Ingår:

  • Backend-endpoint för target-beräkning
  • Binärsöknings-algoritm i rating-calculator
  • Modal-UI per spelare i ratings-tabellen
  • Frontend-validering av inputs
  • Varning vid orealistiska värden (>1050 eller <600)

Ingår inte (möjliga uppföljningar):

  • Spara/historik av target-frågor per spelare
  • Show std-dev range kring kravet (skulle motsvara ±X rating på snittet — kan vara nyttigt men över-engineering för v1)
  • Multi-spelare batch-beräkning
  • Target som inkluderar specifika kända framtida rundor (t.ex. "jag har 2 rundor på 945 inbokade, vad behöver jag på resterande 2?")
  • Grafisk visualisering av rating-utveckling
## Beskrivning Lägg till en funktion där användaren anger en önskad rating och antal rundor (t.ex. **930 på 4 rundor**), och systemet räknar ut vilket snitt spelaren behöver gå på dessa rundor för att hamna på targetratingen efter nästa officiella PDGA-uträkning. Detta är en naturlig förlängning av befintlig "Predicted rating"-funktion: istället för att förutsäga *resultatet* av nuvarande historik, inverterar vi formeln för att hitta vilka *framtida rundor* som krävs för ett önskat resultat. ## Design **Placering:** Ny ikon/knapp i `views/partials/ratings-table.ejs` bredvid Predicted-kolumnen (samma rad som `showDebugInfo`-ikonen). Knappen öppnar en modal — återanvänd modal-mönstret som redan finns i `public/js/players.js` för debug-info. **Modal-innehåll:** - Input 1: Target rating (number, t.ex. `930`) - Input 2: Antal rundor (number, 1–20) - Knapp: "Räkna ut" - Resultatpanel som visar: - **Snittkrav:** `XXX rating` (det enda krävda värdet) - Liten förklaringstext: "För att gå från `<current>` till `<target>` på `<N>` rundor behöver du snitta `XXX` på dessa rundor (beräknat enligt PDGA:s officiella uträkning för nästa publicering)." - **Varning om orealistiskt:** Om `XXX > 1050` (PDGA top-100-nivå) eller `XXX < 600`, visa varning: "⚠️ Detta är ett extremt värde — kontrollera att din target är rimlig." **Layout:** Återanvänd `.card-section` + `.modal` CSS från befintliga modaler. ## Tekniska noteringar ### Backend **Ny endpoint:** `POST /api/calculate-target-rating/:pdgaNumber` i `src/routes/players.js` Request body: `{ targetRating: number, rounds: number }` Response: `{ requiredAverage: number, currentRating: number, predictedRating: number, warning?: string }` **Ny funktion i `src/services/rating-calculator.js`:** ```js calculateRequiredAverage(roundRatings, targetRating, numRounds) → { requiredAverage, warning? } ``` Implementation: **binärsökning** över kandidatratings R i intervallet `[400, 1200]`. För varje R: 1. Bygg `simulatedRounds = roundRatings ∪ [{ rating: R, date: <just före next PDGA-update>, competition: 'simulated' } × numRounds]` 2. Kör befintliga `calculatePredictedRating(simulatedRounds)` — den hanterar redan PDGA-cutoff, outlier-borttagning och dubbelviktning korrekt 3. Jämför returnerat `rating` mot `targetRating` 4. Konvergera tills `|predicted - target| < 0.5` Datum för simulerade rundor sätts till `getNextPDGAUpdateDate() - 1 dag` så de räknas in i nästa publicering. **Varför binärsökning:** Outlier-removal (≥7 rundor) och dubbelviktning (≥9 rundor) gör formeln icke-linjär i R. Binärsökning är ~20 iterationer av en cheap funktion — försumbar kostnad jämfört med en closed-form-lösning som måste specialfalla varje gren. **Edge cases att hantera:** - Spelare utan round_history: returnera fel "ingen historik tillgänglig — refresha rundor först" - Target identiskt med predicted: requiredAverage = predicted (trivialt) - Binärsökningen konvergerar inte (target är ej nåbart inom `[400, 1200]`): returnera närmaste gräns + warning ### Frontend **Ny fil:** lägg till handler i `public/js/players.js` (eller bryt ut till ny `public/js/target-rating.js` om det blir >100 rader): - `openTargetRatingModal(pdgaNumber)` — öppnar modalen, läser nuvarande rating - `calculateTargetRating(pdgaNumber)` — `fetch` till nya endpointen, renderar resultat - Validering: rounds är heltal 1–20, target är 400–1200 **Mall:** Lägg modal-markup i `views/partials/target-rating-modal.ejs` (ny partial), include:a från `views/pages/index.ejs`. **HTMX vs vanilla fetch:** Befintlig modal-logik (`showDebugInfo`) använder vanilla fetch + DOM-manipulation. Följ samma mönster för konsistens. ### Databas Inga schema-ändringar behövs — beräkningen är stateless, vi cachar inte target-resultat (de är cheap att räkna om vid varje input-ändring). ## Scope **Ingår:** - Backend-endpoint för target-beräkning - Binärsöknings-algoritm i rating-calculator - Modal-UI per spelare i ratings-tabellen - Frontend-validering av inputs - Varning vid orealistiska värden (>1050 eller <600) **Ingår inte (möjliga uppföljningar):** - Spara/historik av target-frågor per spelare - Show std-dev range kring kravet (skulle motsvara `±X rating` på snittet — kan vara nyttigt men över-engineering för v1) - Multi-spelare batch-beräkning - Target som inkluderar specifika kända framtida rundor (t.ex. "jag har 2 rundor på 945 inbokade, vad behöver jag på resterande 2?") - Grafisk visualisering av rating-utveckling
shcizo added the enhancement label 2026-05-21 11:00:16 +02:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: shcizo/pdga-rating#2