From 84ebd91929115a393324d8c3a8319d7fb488f4e8 Mon Sep 17 00:00:00 2001 From: Holden Salomon Date: Sun, 14 Jun 2026 19:53:33 -0400 Subject: [PATCH] fix: quality replacement slot floor uses deleted file's score not failed candidate's (#27) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: quality replacement slot floor uses deleted file's score not failed candidate's When a blur-score replacement deletes a low-quality Frigate file but the subsequent upload fails, min_quality_score_for_slot was set to candidate_score (the good file that failed to upload). This filtered out any subsequent candidate that didn't beat the failed upload, even if it was better than the file we just deleted — leaving the freed slot unfilled unnecessarily. The comment on the guard already documented the correct intent: 'require the next candidate to beat the deleted file's score'. Fix: use target_score (the deleted file's blur score) as the floor instead of candidate_score. * chore: bump version to 0.5.4 --- CHANGELOG.md | 6 ++++++ pyproject.toml | 2 +- winnow/executor.py | 2 +- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index db12bf4..310c4f4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.5.4] - 2026-06-14 + +### Fixed + +- **Quality replacement slot floor used wrong score**: when a blur-score replacement deleted a low-quality Frigate file but the subsequent upload failed, `min_quality_score_for_slot` was set to the failed candidate's score rather than the deleted file's score. This caused subsequent candidates that were better than the deleted file (but worse than the failed upload) to be skipped, leaving the freed slot unfilled for the rest of that run. Fixed by using `target_score` (deleted file's score) as the floor, matching the documented intent in the surrounding comment. + ## [0.5.3] - 2026-06-14 ### Fixed diff --git a/pyproject.toml b/pyproject.toml index d5422e0..02f785b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "winnow" -version = "0.5.3" +version = "0.5.4" description = "Selects diverse, high-quality photos from Immich as training data for Frigate face recognition." license = "AGPL-3.0-or-later" requires-python = ">=3.13" diff --git a/winnow/executor.py b/winnow/executor.py index 23c8186..ef77331 100644 --- a/winnow/executor.py +++ b/winnow/executor.py @@ -464,7 +464,7 @@ def upload_to_frigate(jobs: list[dict]) -> None: remove_frigate_file(name, target_frigate_file) person_has_fscores = has_frigate_scores(name) effective_count -= 1 - min_quality_score_for_slot = None if using_fscore else candidate_score + min_quality_score_for_slot = None if using_fscore else target_score else: logger.warning("Failed to delete %s for %s, skipping replacement", target_frigate_file, name) failed_deletes.add(target_frigate_file)