fix(library): count trend never played by set total

Nightly snapshot and live trend point use the set total, as stats do.
Newest point now equals stats "never started". Old rows keep old value.
Refs #43
This commit is contained in:
lobo committed 2026-10-11 12:11:47 +02:00
1 parent 8f884040d8
commit 6215bb0dec
3 files changed
+29 -2

No files matched your search

+2 -1
View File
@@ -124,7 +124,8 @@ flowchart LR
moods, the total playtime and moon trips, the drill-down lists) uses the set total, the view moods, the total playtime and moon trips, the drill-down lists) uses the set total, the view
`user_game_entry_playtime` (V45), the same as the library card, sort and public profile. So do "played", "never `user_game_entry_playtime` (V45), the same as the library card, sort and public profile. So do "played", "never
started" and "under one hour": a set counts once, judged by its set total (also the dashboard "never played" and the started" and "under one hour": a set counts once, judged by its set total (also the dashboard "never played" and the
oldest unplayed game). Only the nightly trend snapshot `never_played` still uses the counted edition's own minutes. oldest unplayed game) and the trend's never played (`backlog_snapshot`); rows written before #43 keep the old value,
they cannot be rebuilt.
Platforms stay per edition: a Steam + PlayStation set counts on both platforms. Platforms stay per edition: a Steam + PlayStation set counts on both platforms.
- **Drill-down**: every countable number opens the games behind it. A `StatKey` (library), `CommunityKey` (landing page) - **Drill-down**: every countable number opens the games behind it. A `StatKey` (library), `CommunityKey` (landing page)
or a Pile Points track/bonus row names a number; its SQL "game set" is the same text the number counts, so the or a Pile Points track/bonus row names a number; its SQL "game set" is the same text the number counts, so the
@@ -12,6 +12,7 @@ import java.util.List;
/** /**
* The daily pile size per user, table backlog_snapshot. Counts listed games only, like {@link UserGameRepository#LISTED}, and a linked set once (V28). * The daily pile size per user, table backlog_snapshot. Counts listed games only, like {@link UserGameRepository#LISTED}, and a linked set once (V28).
* Never played is judged by the set total (V45), like {@link UserGameRepository#countListedNeverPlayed}.
*/ */
@Repository @Repository
@RequiredArgsConstructor @RequiredArgsConstructor
@@ -19,11 +20,12 @@ public class BacklogSnapshotRepository {
private static final String PILE_COLUMNS = """ private static final String PILE_COLUMNS = """
count(*) filter (where ug.status = 'BACKLOG' and cg.appid is null) as backlog_games, count(*) filter (where ug.status = 'BACKLOG' and cg.appid is null) as backlog_games,
count(*) filter (where ug.playtime_minutes = 0) as never_played, count(*) filter (where ep.playtime_minutes = 0) as never_played,
coalesce(sum(t.normally_minutes) filter (where ug.status = 'BACKLOG' and cg.appid is null), 0) as backlog_minutes, coalesce(sum(t.normally_minutes) filter (where ug.status = 'BACKLOG' and cg.appid is null), 0) as backlog_minutes,
count(*) as listed_games count(*) as listed_games
from counted_user_game ug left join game_time_to_beat t on t.appid = ug.appid from counted_user_game ug left join game_time_to_beat t on t.appid = ug.appid
left join coming_game cg on cg.appid = ug.appid left join coming_game cg on cg.appid = ug.appid
left join user_game_entry_playtime ep on ep.user_id = ug.user_id and ep.appid = ug.appid
"""; """;
private final NamedParameterJdbcTemplate jdbc; private final NamedParameterJdbcTemplate jdbc;
@@ -86,6 +86,30 @@ class BacklogSnapshotRepositoryTest extends AbstractPersistenceTest {
assertThat(repository.findSince(1L, DAY)).containsExactly(new UserStats.PileDay(DAY, 1, 1, 600)); assertThat(repository.findSince(1L, DAY)).containsExactly(new UserStats.PileDay(DAY, 1, 1, 600));
} }
private void psnPlay(long appid, int minutes) {
jdbc.update("insert into user_psn_title (user_id, psn_key, state, appid, platform, playtime_minutes) "
+ "values (1, ?, 'STEAM_OWNED', ?, 'PS5', ?)", "c" + appid, appid, minutes);
}
@Test
void writeAll_ASetPlayedOnlyOnPlayStationIsNotNeverPlayed() {
userGameRepository.saveAllAndFlush(List.of(game(1L, 220L, 0, GameStatus.BACKLOG), game(1L, 620L, 0, GameStatus.BACKLOG)));
psnPlay(220L, 120);
repository.writeAll(DAY);
assertThat(repository.findSince(1L, DAY)).extracting(UserStats.PileDay::neverPlayed).containsExactly(1L);
assertThat(userGameRepository.countListedNeverPlayed(1L)).isEqualTo(1);
}
@Test
void live_UsesTheSetTotal() {
userGameRepository.saveAllAndFlush(List.of(game(1L, 220L, 0, GameStatus.BACKLOG), game(1L, 620L, 0, GameStatus.BACKLOG)));
psnPlay(220L, 120);
assertThat(repository.live(1L, DAY).neverPlayed()).isEqualTo(1).isEqualTo(userGameRepository.countListedNeverPlayed(1L));
}
@Test @Test
void writeAll_SecondRunOnTheSameDay_ReplacesTheRow() { void writeAll_SecondRunOnTheSameDay_ReplacesTheRow() {
UserGame game = game(1L, 220L, 0, GameStatus.BACKLOG); UserGame game = game(1L, 220L, 0, GameStatus.BACKLOG);