Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 40 additions & 8 deletions app/Services/Builders/RankReportHistoryBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -315,12 +315,12 @@ protected function updateThousandVotesIncremental(array $cachedSummary)
->get();

$newCount = $newVotes->count();

if ($newCount == 0) {
return;
}


// 如果剛好是1000則直接用newVotes的結果
// 如果一次抓回來就滿1000筆(或更多),直接覆蓋成最新的這1000筆
if ($newCount === 1000) {
$winCount = $newVotes->where('winner_id', $this->report->element_id)->count();
$loseCount = $newVotes->where('loser_id', $this->report->element_id)->count();
Expand All @@ -330,34 +330,66 @@ protected function updateThousandVotesIncremental(array $cachedSummary)
return;
}

// 計算新票數的勝敗
$winNew = $newVotes->where('winner_id', $this->report->element_id)->count();
$loseNew = $newVotes->where('loser_id', $this->report->element_id)->count();

$winOutdated = 0;
$loseOutdated = 0;

// 計算目前的總票數與預期總票數
$currentTotal = $winCount + $loseCount;
$totalAfterAdd = $currentTotal + $newCount;

// 只有當 (原票數 + 新票數) > 1000 時,才需要移除舊資料
// 需要移除的數量 = 總數 - 1000
$countToRemove = max(0, $totalAfterAdd - 1000);

// 初始化 newMinId,預設維持原狀
$newMinId = $minId;

if ($newCount > 0 && $minId > 0) {
// Fetch the oldest portion plus the next one to derive newMinId
// [情況 A] 總數超過 1000,需要移除舊的 ($countToRemove > 0)
// 且必須原本就有 minId (有資料可移)
if ($countToRemove > 0 && $minId > 0) {
// Fetch the oldest portion needed to be removed plus one to find new boundary
$outdatedVotes = $this->getThousandVotesBaseQuery()
->where('game_1v1_rounds.id', '>=', $minId)
->orderBy('game_1v1_rounds.id')
->limit($newCount + 1)
->limit($countToRemove + 1) // 多抓一筆用來定位新的 min_id
->get();

if ($outdatedVotes->isNotEmpty()) {
$outdatedSlice = $outdatedVotes->take($newCount);
// 取出真正要扣掉的那幾筆
$outdatedSlice = $outdatedVotes->take($countToRemove);
$winOutdated = $outdatedSlice->where('winner_id', $this->report->element_id)->count();
$loseOutdated = $outdatedSlice->where('loser_id', $this->report->element_id)->count();

$newMinId = $outdatedVotes->last()->id;
// 新的 min_id 是被移除區段後的下一筆資料 ID
// 如果 outdatedVotes 數量足夠,last() 就是新的起點
if ($outdatedVotes->count() > $countToRemove) {
$newMinId = $outdatedVotes->last()->id;
} else {
// 防呆:如果資料庫剛好斷層,取最後一筆
$newMinId = $outdatedVotes->last()->id;
}
}
}
// [情況 B] 總數還沒滿 1000,不需要移除舊資料
// 但如果是第一次建立 ($minId == 0),需要設定 minId
elseif ($minId == 0 && $newCount > 0) {
Copy link

Copilot AI Feb 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inconsistent comparison operators for checking minId. Line 353 uses strict comparison ($minId > 0) while line 379 uses loose equality ($minId == 0). For consistency and to avoid potential type coercion issues, use the same comparison style throughout. Consider using strict equality (===) for the zero check on line 379.

Suggested change
elseif ($minId == 0 && $newCount > 0) {
elseif ($minId === 0 && $newCount > 0) {

Copilot uses AI. Check for mistakes.
// 因為 $newVotes 是 orderByDesc,所以最後一筆是 ID 最小的
$newMinId = $newVotes->last()->id;
}
Comment on lines +379 to 382
Copy link

Copilot AI Feb 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment states this handles the first time case when $minId == 0, but there's a potential edge case. If the cached summary has min_id set to 0 but has non-zero win/lose counts, this could indicate data corruption or an invalid state. The code should either validate this scenario doesn't happen, or handle it more explicitly.

Consider adding a check: if $minId == 0 but $currentTotal > 0, this indicates an inconsistent state that should either be logged or trigger a rebuild from scratch.

Copilot uses AI. Check for mistakes.

// 更新數據
$winCount = max(0, $winCount - $winOutdated + $winNew);
$loseCount = max(0, $loseCount - $loseOutdated + $loseNew);

// maxId 永遠更新為新的最高 ID
$maxId = $newVotes->isNotEmpty() ? max($maxId, $newVotes->first()->id) : $maxId;
$minId = $newMinId ?: $minId;

// minId 使用計算後的新起點
$minId = $newMinId;

$this->saveThousandVotesHistory($winCount, $loseCount, $maxId, $minId);
}
Expand Down