- immich_api: `score or confidence` treated 0.0 score as falsy; use explicit None check
- diversity: same falsy-zero fix in _get_face_confidence
- diversity: _crop_face_from_thumbnail scale loop now filters by person_id (was
using first person's imageWidth/imageHeight regardless of target in group photos)
- jobs: partially-trained auto mode kept limit="auto" for adaptive stopping, then
caps result to remaining capacity (was converting to int, silently disabling FPS
adaptive threshold and early-stop)
- embeddings: _suppress_output finally block wraps first dup2 in try/finally so
stderr is always restored even if stdout restore raises OSError
- Dockerfile: ldconfig find uses python3.* glob instead of hardcoded python3.13
- scheduler: sleep until next_run instead of fixed 60s; eliminates late-fire jitter
and unnecessary wakeups on long schedules
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Bugs fixed (from high-effort review):
- InsightFace CPU fallback now passes providers=['CPUExecutionProvider'] and
wraps in _suppress_output() so broken GPU drivers don't cause fallback to
try the same broken provider again, and C-extension noise stays suppressed
- scheduler.py: BaseException → Exception (KeyboardInterrupt already re-raised;
winnow has no sys.exit() calls, so SystemExit would not occur, but Exception
is the correct scope)
- compose.yml: fix inverted AUTO_MODE comment (docker run -it enables
interactive mode via TTY, not non-interactive)
Model loading logging (embeddings.py):
- InsightFace: disk cache check, "not cached — downloading now (~300 MB)",
"loading into memory on GPU/CPU...", "ready on GPU/CPU (Xs)"
- SigLIP: same treatment; cache path derived dynamically from model_name
via HuggingFace slug convention (models--org--model) so it stays correct
if the model variant ever changes
Logging level audit (INFO/DEBUG/WARNING/ERROR):
- diversity.py: internal algo steps (clustering, medoids, adaptive threshold,
auto-stop decision) → DEBUG; final selection summaries stay INFO
- immich_api.py: "Fetching assets" and "Retained N assets" → DEBUG (callers
already print this to the console via rprint)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
embeddings.py: face embedding cache used 'immich' for lookup but
'insightface' for storage, so the cache was never hit for locally-
computed embeddings. Unified to 'insightface'/'siglip' throughout.
This affects all users since ENABLE_CACHE now defaults to true.
scheduler.py: INSIGHTFACE_HOME=/models/.insightface was having
'.insightface' appended again, making buffalo_l check always report
'will download'. Also catch BaseException (not just Exception) so a
SystemExit from a library call can't silently kill all future runs.
log_config.py: handlers.clear() abandoned open FileHandler fds on
each scheduled main() call. Close each handler properly before removal.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- __init__.py: derive __version__ from importlib.metadata instead of
a hardcoded "0.1.0" that was six releases out of date
- scheduler.py: move winnow.cli import to module top (no more noqa);
clean up redundant bool variables in check_models
- tests/test_quality.py: 17 tests covering all five quality check
functions individually plus assess_quality integration cases
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- winnow/logging.py → winnow/log_config.py: avoids shadowing the stdlib
logging module; log file renamed from immich_export.log to winnow.log
- scheduler.py: run main() in-process instead of subprocess.run so
InsightFace and SigLIP models stay resident in memory across scheduled
runs (hundreds of MB load, previously reloaded every run)
- .gitignore: replaced 200-line boilerplate with ~30 project-relevant
patterns; removed Django/Flask/Redis/RabbitMQ/Scrapy/etc. noise
- .python-version: untracked (redundant with requires-python in
pyproject.toml; kept in .gitignore for local pyenv users)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>