fix(stats): preserve lifetime history across anime merges and moves

- Recompute anime aggregates from retained lifetime media summaries
- Prevent sequel title evidence and AniList conflicts from misattributing entries
This commit is contained in:
2026-08-14 00:17:14 -07:00
parent 93bdff1ca2
commit 6311838ef6
9 changed files with 263 additions and 45 deletions
@@ -1,5 +1,5 @@
import type { DatabaseSync } from './sqlite';
import { rebuildLifetimeSummariesInTransaction } from './lifetime';
import { recomputeLifetimeAnimeAggregatesInTransaction } from './lifetime';
import { toDbTimestamp } from './query-shared';
import { nowMs } from './time';
@@ -151,8 +151,9 @@ function absorbAnimeMetadata(
* is repointed, metadata the target is missing is inherited from the sources,
* and the emptied source rows are deleted.
*
* Assumes the caller already holds a write transaction and rebuilds the
* lifetime summaries afterwards; use {@link mergeAnimeRecords} otherwise.
* Assumes the caller already holds a write transaction and refreshes the
* per-anime lifetime aggregates afterwards; use {@link mergeAnimeRecords}
* otherwise.
*/
export function mergeAnimeRecordsInTransaction(
db: DatabaseSync,
@@ -237,7 +238,7 @@ export function mergeAnimeRecords(
return runInTransaction(db, () => {
const summary = mergeAnimeRecordsInTransaction(db, targetAnimeId, sourceAnimeIds);
if (summary.mergedAnimeIds.length > 0) {
rebuildLifetimeSummariesInTransaction(db);
recomputeLifetimeAnimeAggregatesInTransaction(db);
}
return summary;
});
@@ -286,7 +287,7 @@ export function moveVideoToAnime(
removedPreviousAnime = true;
}
rebuildLifetimeSummariesInTransaction(db);
recomputeLifetimeAnimeAggregatesInTransaction(db);
return { targetAnimeId, previousAnimeId, removedPreviousAnime };
});
}