diff --git a/domains/games/apis/one_d4/src/main/java/com/muchq/games/one_d4/worker/IndexWorker.java b/domains/games/apis/one_d4/src/main/java/com/muchq/games/one_d4/worker/IndexWorker.java index 991c1cb0..a1698b19 100644 --- a/domains/games/apis/one_d4/src/main/java/com/muchq/games/one_d4/worker/IndexWorker.java +++ b/domains/games/apis/one_d4/src/main/java/com/muchq/games/one_d4/worker/IndexWorker.java @@ -214,6 +214,7 @@ private void flushBatch( Map>> occurrencesBatch) { if (featureBatch.isEmpty()) return; gameFeatureStore.insertBatch(featureBatch); + gameFeatureStore.deleteOccurrencesByGameUrls(new ArrayList<>(occurrencesBatch.keySet())); gameFeatureStore.insertOccurrencesBatch(occurrencesBatch); featureBatch.clear(); occurrencesBatch.clear(); diff --git a/domains/games/apis/one_d4/src/test/java/com/muchq/games/one_d4/db/GameFeatureDaoTest.java b/domains/games/apis/one_d4/src/test/java/com/muchq/games/one_d4/db/GameFeatureDaoTest.java index e88f3c6e..0815625b 100644 --- a/domains/games/apis/one_d4/src/test/java/com/muchq/games/one_d4/db/GameFeatureDaoTest.java +++ b/domains/games/apis/one_d4/src/test/java/com/muchq/games/one_d4/db/GameFeatureDaoTest.java @@ -572,6 +572,29 @@ public void query_paginatesStablyWhenPlayedAtIsEqual() { assertThat(page2.get(0).gameUrl()).isEqualTo("https://chess.com/game/zzz-last"); } + @Test + public void deleteOccurrencesByGameUrls_thenReinsert_doesNotDuplicate() { + // Regression test: re-indexing a partial month must not accumulate duplicate occurrences. + // The fix is in IndexWorker.flushBatch: delete occurrences for each game_url before inserting. + String gameUrl = "https://chess.com/game/reindex-dedup"; + dao.insertBatch(List.of(createGame(gameUrl))); + + GameFeatures.MotifOccurrence pin = + new GameFeatures.MotifOccurrence( + 5, 3, "white", "Pin on c6", null, "Bb5", "nc6", false, false, "ABSOLUTE"); + Map>> occurrences = + Map.of(gameUrl, Map.of(Motif.PIN, List.of(pin))); + + // First index run + dao.insertOccurrencesBatch(occurrences); + // Simulate re-index: delete then re-insert (what flushBatch now does) + dao.deleteOccurrencesByGameUrls(List.of(gameUrl)); + dao.insertOccurrencesBatch(occurrences); + + Map>> result = dao.queryOccurrences(List.of(gameUrl)); + assertThat(result.get(gameUrl).get("pin")).hasSize(1); + } + private GameFeature createGame(String url) { return new GameFeature( null,