fix: invalidera predicted rating när PDGA-uträkningscykeln rullat över #29

Closed
opened 2026-06-09 10:48:48 +02:00 by shcizo · 0 comments
Owner

Beskrivning

Det lagrade predicted_rating räknas inte om/invalideras automatiskt när PDGA:s uträkningscykel passerat (andra tisdagen i månaden). Vi visar då en inaktuell prediktion som beräknades mot förra cykelns fönster.

calculatePredictedRating() filtrerar rundor mot getNextPDGAUpdateDate() (r.date < nextUpdateDate, src/services/rating-calculator.js:98). När cykeln rullar över ändras nextUpdateDate → hela fönstret (12-mån-cutoff, recent-weighting) ska beräknas mot ett nytt datum. Men cachat värde i DB uppdateras bara när predicted_rating är null/0 (src/services/player-service.js:45) eller vid manuell round-history-refresh (src/routes/players.js:401). Det finns ingen kolumn som spårar när/mot vilken cykel prediktionen beräknades, så vi kan inte avgöra om den är inaktuell.

Vi har idag last_round_update (src/db.js:18), men den går inte att återanvända för detta:

  • Den sätts bara i den manuella refresh-vägen (src/routes/players.js:382), inte i den lazy omräkningen i getPredictedRatingFromDB (src/services/player-service.js:152-154).
  • Den är dessutom nyckeln för 24h-cooldownen (src/routes/players.js:334) — att överlasta den skulle korrumpera rate-limitingen.

Förväntat beteende

När getPreviousPDGAUpdateDate() (senaste passerade andra-tisdagen) är senare än när prediktionen beräknades, ska det cachade värdet betraktas som inaktuellt och inte visas — UI visar tills nästa manuella refresh producerar ett nytt värde.

Vi räknar medvetet inte om automatiskt vid läsning: cachad round_history kan vara lika gammal, så en omräkning direkt efter en officiell uträkning kan ge en missvisande siffra. är en ärligare signal om att en refresh behövs.

Teknisk orsak

  • src/db.js (players-schema, ~rad 11-78): saknar tidsstämpel för när predicted beräknades.
  • src/models/player.js:190-201 (savePredictedRatingToDB): skriver ingen tidsstämpel.
  • src/services/player-service.js:35-85 (getPlayerDataFromDB): recompute-gren triggas på null/0 (rad 45) utan staleness-koll.
  • src/services/rating-calculator.js:40-71 (getNextPDGAUpdateDate): finns för nästa uträkning, men ingen motsvarighet för föregående.

Constraint: Appen har ingen scheduler/cron — invalidering måste ske lazy (vid läsning), inte som tidsstyrt jobb.

Förslag till lösning

  1. Ny kolumn predicted_calculated_at DATETIME i players (migration i src/db.js enligt befintligt ALTER TABLE-mönster).
  2. Skriv predicted_calculated_at = CURRENT_TIMESTAMP i savePredictedRatingToDB (src/models/player.js:190) — choke point som täcker både refresh-vägen (players.js:403) och lazy-vägen (player-service.js:154).
  3. Ny helper getPreviousPDGAUpdateDate() i src/services/rating-calculator.js (senaste passerade andra-tisdagen). Delas med issue 2.
  4. I getPlayerDataFromDB (src/services/player-service.js:35-85): om predicted_calculated_at < getPreviousPDGAUpdateDate() → returnera predictedRating (+ std_dev/cutoff_rating/excluded_rounds_count) som null. Räkna inte om, skriv inte till DB. Staleness-checken ligger före den befintliga recompute-grenen (rad 45) så den inte väcks. Recompute behålls bara för genuint aldrig-beräknade spelare.

Scope

  • Ingår: schema-migration (ny kolumn), tidsstämpel i savePredictedRatingToDB, getPreviousPDGAUpdateDate()-helper, staleness-check i läsväg som nollar i svaret.
  • Ingår INTE: hämtning av nya rundor från PDGA, eller skip-logik baserat på om nya tävlingar tillkommit (se separat issue om "hoppa över predicted när inga nya tävlingar tillkommit").
## Beskrivning Det lagrade `predicted_rating` räknas inte om/invalideras automatiskt när PDGA:s uträkningscykel passerat (andra tisdagen i månaden). Vi visar då en inaktuell prediktion som beräknades mot förra cykelns fönster. `calculatePredictedRating()` filtrerar rundor mot `getNextPDGAUpdateDate()` (`r.date < nextUpdateDate`, `src/services/rating-calculator.js:98`). När cykeln rullar över ändras `nextUpdateDate` → hela fönstret (12-mån-cutoff, recent-weighting) ska beräknas mot ett nytt datum. Men cachat värde i DB uppdateras bara när `predicted_rating` är `null`/`0` (`src/services/player-service.js:45`) eller vid manuell round-history-refresh (`src/routes/players.js:401`). Det finns **ingen kolumn som spårar när/mot vilken cykel prediktionen beräknades**, så vi kan inte avgöra om den är inaktuell. Vi har idag `last_round_update` (`src/db.js:18`), men den går **inte** att återanvända för detta: - Den sätts bara i den manuella refresh-vägen (`src/routes/players.js:382`), inte i den lazy omräkningen i `getPredictedRatingFromDB` (`src/services/player-service.js:152-154`). - Den är dessutom nyckeln för 24h-cooldownen (`src/routes/players.js:334`) — att överlasta den skulle korrumpera rate-limitingen. ## Förväntat beteende När `getPreviousPDGAUpdateDate()` (senaste passerade andra-tisdagen) är **senare** än när prediktionen beräknades, ska det cachade värdet betraktas som inaktuellt och **inte visas** — UI visar `—` tills nästa manuella refresh producerar ett nytt värde. Vi räknar medvetet **inte** om automatiskt vid läsning: cachad `round_history` kan vara lika gammal, så en omräkning direkt efter en officiell uträkning kan ge en missvisande siffra. `—` är en ärligare signal om att en refresh behövs. ## Teknisk orsak - `src/db.js` (players-schema, ~rad 11-78): saknar tidsstämpel för när predicted beräknades. - `src/models/player.js:190-201` (`savePredictedRatingToDB`): skriver ingen tidsstämpel. - `src/services/player-service.js:35-85` (`getPlayerDataFromDB`): recompute-gren triggas på `null`/`0` (rad 45) utan staleness-koll. - `src/services/rating-calculator.js:40-71` (`getNextPDGAUpdateDate`): finns för *nästa* uträkning, men ingen motsvarighet för *föregående*. **Constraint:** Appen har **ingen scheduler/cron** — invalidering måste ske lazy (vid läsning), inte som tidsstyrt jobb. ## Förslag till lösning 1. Ny kolumn `predicted_calculated_at DATETIME` i `players` (migration i `src/db.js` enligt befintligt `ALTER TABLE`-mönster). 2. Skriv `predicted_calculated_at = CURRENT_TIMESTAMP` i `savePredictedRatingToDB` (`src/models/player.js:190`) — choke point som täcker **både** refresh-vägen (`players.js:403`) och lazy-vägen (`player-service.js:154`). 3. Ny helper `getPreviousPDGAUpdateDate()` i `src/services/rating-calculator.js` (senaste passerade andra-tisdagen). **Delas med issue 2.** 4. I `getPlayerDataFromDB` (`src/services/player-service.js:35-85`): om `predicted_calculated_at < getPreviousPDGAUpdateDate()` → returnera `predictedRating` (+ `std_dev`/`cutoff_rating`/`excluded_rounds_count`) som `null`. **Räkna inte om, skriv inte till DB.** Staleness-checken ligger *före* den befintliga recompute-grenen (rad 45) så den inte väcks. Recompute behålls bara för genuint aldrig-beräknade spelare. ## Scope - Ingår: schema-migration (ny kolumn), tidsstämpel i `savePredictedRatingToDB`, `getPreviousPDGAUpdateDate()`-helper, staleness-check i läsväg som nollar i svaret. - Ingår INTE: hämtning av nya rundor från PDGA, eller skip-logik baserat på om nya tävlingar tillkommit (se separat issue om "hoppa över predicted när inga nya tävlingar tillkommit").
shcizo added the bug label 2026-06-09 10:48:48 +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#29