fix: predicted_rating wipas av INSERT OR REPLACE i savePlayerToDB #11

Closed
opened 2026-05-22 09:46:56 +02:00 by shcizo · 1 comment
Owner

Beskrivning

Predicted rating försvinner för alla spelare samtidigt när topbarens "Refresh all"-knapp klickas (eller annan kod-väg som anropar savePlayerToDB för existerande spelare). Visas i UI som tom cell .

Tidigare antogs detta vara ett sporadiskt problem — utforskning har nu identifierat rotorsaken (se diskussion).

Rotorsak

src/models/player.js:20-22:

INSERT OR REPLACE INTO players (pdga_number, name, current_rating, rating_change, last_updated)
VALUES (?, ?, ?, ?, datetime('now'))

INSERT OR REPLACE raderar hela den existerande raden och infogar en ny. Kolumner som inte är med i VALUES återgår till DEFAULT (NULL):

  • predicted_rating
  • std_dev
  • last_round_update
  • excluded_rounds_count
  • cutoff_rating

Reproduktion

  1. Lägg till några spelare och kör refresh-round-history så de har ett predicted_rating
  2. Klicka "Refresh all" i topbaren
  3. Predicted-kolumnen visar för alla

Fix

Byt INSERT OR REPLACE mot SQLite UPSERT som explicit listar endast de fält som ska uppdateras vid konflikt:

INSERT INTO players (pdga_number, name, current_rating, rating_change, last_updated)
VALUES (?, ?, ?, ?, datetime('now'))
ON CONFLICT(pdga_number) DO UPDATE SET
  name = excluded.name,
  current_rating = excluded.current_rating,
  rating_change = excluded.rating_change,
  last_updated = excluded.last_updated

Detta bevarar predicted_rating, std_dev, last_round_update, excluded_rounds_count, cutoff_rating för existerande spelare.

Sekundär konsekvens (att verifiera)

last_round_update nukas också i samma operation → 24h-cooldownen för /api/refresh-round-history/:pdgaNumber släpper falskt. Lösningen ovan fixar även detta som biprodukt.

Scope

Ingår:

  • Ändra savePlayerToDB i src/models/player.js:17-28 till UPSERT
  • Granska om andra funktioner i src/models/player.js har samma INSERT OR REPLACE-mönster (t.ex. saveRoundHistoryToDB, course-modeller) — fixa eller motivera varför inte
  • Manuell verifiering enligt repro-stegen ovan

Ingår inte:

  • Auto-beräkning av predicted_rating vid /api/add-player (separat issue om vi vill ha det)
  • 0 → null-konverteringen i getPlayerDataFromDB:45 (kvarstår som korrekt vid otillräcklig data — om någon spelare faktiskt har < 8 ronder ska de visa )
  • Refresh-flödets felhantering vid scraping-fail

Acceptanskriterier

  • savePlayerToDB använder UPSERT som bevarar predicted-relaterade kolumner
  • Manuell verifiering: predicted_rating överlever ett klick på "Refresh all"
  • last_round_update överlever också (cooldown fungerar fortfarande som tänkt)
  • Inga andra modeller har samma INSERT OR REPLACE-fälla på rader med data som ska bevaras
## Beskrivning Predicted rating försvinner för **alla spelare samtidigt** när topbarens "Refresh all"-knapp klickas (eller annan kod-väg som anropar `savePlayerToDB` för existerande spelare). Visas i UI som tom cell `—`. Tidigare antogs detta vara ett sporadiskt problem — utforskning har nu identifierat rotorsaken (se diskussion). ## Rotorsak `src/models/player.js:20-22`: ```javascript INSERT OR REPLACE INTO players (pdga_number, name, current_rating, rating_change, last_updated) VALUES (?, ?, ?, ?, datetime('now')) ``` `INSERT OR REPLACE` raderar hela den existerande raden och infogar en ny. Kolumner som inte är med i `VALUES` återgår till DEFAULT (NULL): - `predicted_rating` - `std_dev` - `last_round_update` - `excluded_rounds_count` - `cutoff_rating` ## Reproduktion 1. Lägg till några spelare och kör refresh-round-history så de har ett predicted_rating 2. Klicka "Refresh all" i topbaren 3. Predicted-kolumnen visar `—` för alla ## Fix Byt `INSERT OR REPLACE` mot SQLite UPSERT som explicit listar endast de fält som ska uppdateras vid konflikt: ```sql INSERT INTO players (pdga_number, name, current_rating, rating_change, last_updated) VALUES (?, ?, ?, ?, datetime('now')) ON CONFLICT(pdga_number) DO UPDATE SET name = excluded.name, current_rating = excluded.current_rating, rating_change = excluded.rating_change, last_updated = excluded.last_updated ``` Detta bevarar `predicted_rating`, `std_dev`, `last_round_update`, `excluded_rounds_count`, `cutoff_rating` för existerande spelare. ## Sekundär konsekvens (att verifiera) `last_round_update` nukas också i samma operation → 24h-cooldownen för `/api/refresh-round-history/:pdgaNumber` släpper falskt. Lösningen ovan fixar även detta som biprodukt. ## Scope **Ingår:** - Ändra `savePlayerToDB` i `src/models/player.js:17-28` till UPSERT - Granska om andra funktioner i `src/models/player.js` har samma `INSERT OR REPLACE`-mönster (t.ex. `saveRoundHistoryToDB`, course-modeller) — fixa eller motivera varför inte - Manuell verifiering enligt repro-stegen ovan **Ingår inte:** - Auto-beräkning av predicted_rating vid `/api/add-player` (separat issue om vi vill ha det) - 0 → null-konverteringen i `getPlayerDataFromDB:45` (kvarstår som korrekt vid otillräcklig data — om någon spelare faktiskt har < 8 ronder ska de visa `—`) - Refresh-flödets felhantering vid scraping-fail ## Acceptanskriterier - [ ] `savePlayerToDB` använder UPSERT som bevarar predicted-relaterade kolumner - [ ] Manuell verifiering: predicted_rating överlever ett klick på "Refresh all" - [ ] `last_round_update` överlever också (cooldown fungerar fortfarande som tänkt) - [ ] Inga andra modeller har samma INSERT OR REPLACE-fälla på rader med data som ska bevaras
shcizo added the bug label 2026-05-22 09:46:56 +02:00
Author
Owner

Rotorsak hittad — ingen ytterligare diagnostik behövs

Ny observation: alla predicted_rating-värden var borta samtidigt efter några dagar utan att ha besökt sidan. Det pekade mot global invalidation, inte spelarspecifika edge cases. Utforskning bekräftar rotorsaken.

Smoking gun

src/models/player.js:20-22:

INSERT OR REPLACE INTO players (pdga_number, name, current_rating, rating_change, last_updated)
VALUES (?, ?, ?, ?, datetime('now'))

INSERT OR REPLACE i SQLite är inte en mjuk upsert — den raderar hela den existerande raden och infogar en ny. Alla kolumner som inte är med i VALUES-listan får sina DEFAULT-värden tillbaka. Här blir det NULL för:

  • predicted_rating
  • std_dev
  • last_round_update
  • excluded_rounds_count
  • cutoff_rating

Triggerväg

savePlayerToDB anropas från:

  • /api/add-player (src/routes/players.js:239) — manuellt
  • /api/refresh-player/:pdgaNumber (src/routes/players.js:261) — manuellt
  • /api/refresh-all (src/routes/players.js:24) → refreshAllPlayersInDB()scrapePDGARating()savePlayerToDB() för varje spelare i DB (src/services/player-service.js:98,300)

Topbar-knappen "Refresh all" (views/partials/topbar.ejs:34,64) triggar /api/refresh-all. Ett enda klick → predicted_rating wipas för hela tabellen.

Sekundära konsekvenser

  • last_round_update nukas också → 24h-cooldownen släpper, men predicted räknas inte om förrän man manuellt klickar refresh-round-history per spelare
  • Lazy-recompute i getPlayerDataFromDB (src/services/player-service.js:45-51) försöker beräkna om, men getPredictedRatingFromDB returnerar 0 om round_history-tabellen är tom för spelaren → 0 → konverteras till null i UI

Förslag på fix

SQLite UPSERT (renast, säger explicit vad som ska uppdateras):

INSERT INTO players (pdga_number, name, current_rating, rating_change, last_updated)
VALUES (?, ?, ?, ?, datetime('now'))
ON CONFLICT(pdga_number) DO UPDATE SET
  name = excluded.name,
  current_rating = excluded.current_rating,
  rating_change = excluded.rating_change,
  last_updated = excluded.last_updated

Detta lämnar predicted_rating, std_dev, last_round_update, excluded_rounds_count, cutoff_rating orörda för existerande spelare.

Konsekvens för denna issue

Diagnostik behövs inte längre — vi har rotorsaken. Issuen omklassificeras till fix-issue (se uppdaterad titel/body).

## Rotorsak hittad — ingen ytterligare diagnostik behövs Ny observation: alla predicted_rating-värden var borta samtidigt efter några dagar utan att ha besökt sidan. Det pekade mot global invalidation, inte spelarspecifika edge cases. Utforskning bekräftar rotorsaken. ### Smoking gun `src/models/player.js:20-22`: ```javascript INSERT OR REPLACE INTO players (pdga_number, name, current_rating, rating_change, last_updated) VALUES (?, ?, ?, ?, datetime('now')) ``` `INSERT OR REPLACE` i SQLite är **inte** en mjuk upsert — den raderar hela den existerande raden och infogar en ny. Alla kolumner som inte är med i `VALUES`-listan får sina DEFAULT-värden tillbaka. Här blir det `NULL` för: - `predicted_rating` - `std_dev` - `last_round_update` - `excluded_rounds_count` - `cutoff_rating` ### Triggerväg `savePlayerToDB` anropas från: - `/api/add-player` (`src/routes/players.js:239`) — manuellt - `/api/refresh-player/:pdgaNumber` (`src/routes/players.js:261`) — manuellt - `/api/refresh-all` (`src/routes/players.js:24`) → `refreshAllPlayersInDB()` → `scrapePDGARating()` → `savePlayerToDB()` för **varje spelare i DB** (`src/services/player-service.js:98,300`) Topbar-knappen "Refresh all" (`views/partials/topbar.ejs:34,64`) triggar `/api/refresh-all`. Ett enda klick → predicted_rating wipas för hela tabellen. ### Sekundära konsekvenser - `last_round_update` nukas också → 24h-cooldownen släpper, men predicted räknas inte om förrän man manuellt klickar refresh-round-history per spelare - Lazy-recompute i `getPlayerDataFromDB` (`src/services/player-service.js:45-51`) försöker beräkna om, men `getPredictedRatingFromDB` returnerar 0 om round_history-tabellen är tom för spelaren → 0 → konverteras till `null` i UI ### Förslag på fix SQLite UPSERT (renast, säger explicit vad som ska uppdateras): ```sql INSERT INTO players (pdga_number, name, current_rating, rating_change, last_updated) VALUES (?, ?, ?, ?, datetime('now')) ON CONFLICT(pdga_number) DO UPDATE SET name = excluded.name, current_rating = excluded.current_rating, rating_change = excluded.rating_change, last_updated = excluded.last_updated ``` Detta lämnar `predicted_rating`, `std_dev`, `last_round_update`, `excluded_rounds_count`, `cutoff_rating` orörda för existerande spelare. ### Konsekvens för denna issue Diagnostik behövs inte längre — vi har rotorsaken. Issuen omklassificeras till fix-issue (se uppdaterad titel/body).
shcizo changed title from chore: utred och logga var predicted rating försvinner to fix: predicted_rating wipas av INSERT OR REPLACE i savePlayerToDB 2026-06-08 09:18:02 +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#11