ruff isort orders Tagger before TagPrediction (its case-insensitive
tiebreak). One-char autofix; ruff check now clean across backend/tests/alembic.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
import_media_file now enqueues tag_and_embed alongside generate_thumbnail
after a successful import. scripts/download_models.py snapshots Camie +
SigLIP into /models, idempotent (skips when present). The ml-worker
entrypoint runs it before starting the Celery worker so a fresh /models
volume self-heals on first boot. Downloader tests are pure-logic (no
network in CI).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
apply_allowlist_tags: 4 modes (tag-only / image-only / both / full sweep),
matches a tag to a prediction either by direct name or via alias
(name, category) resolution, gates on per-tag min_confidence, skips
applied/rejected, applies source='ml_auto'. recompute_centroid /
recompute_centroids: async-bridged calls into CentroidService, delta-gated.
Beat: daily backfill, daily centroid recompute, daily allowlist sweep.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
tag_and_embed: Camie + SigLIP on one image (video → 10-frame sample,
max-pool tags, mean-pool embeddings), stores predictions/embedding with
model versions, then enqueues per-image allowlist apply. backfill:
keyset-paginated discovery of images missing predictions/embeddings for
the current model versions (restart-safe). apply_allowlist_tags stub
included so .delay() resolves between commits (filled in Task 9).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
AllowlistService: accept (apply ml_accepted + add to allowlist + clear
rejection; returns whether newly-added so API can kick retro-apply),
add_alias_and_accept, dismiss, reject_applied_tag (remove + record
rejection so the allowlist won't re-apply), threshold update, remove,
list_all.
TagService.rename: refuses on (name, kind, fandom_id) collision with a
message pointing at FC-2c merge. Tests marked integration.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The read path: load tagger_predictions, drop unsurfaced categories
(rating/meta/year), apply per-category thresholds, batch-resolve aliases,
skip applied + rejected, augment with centroid hits above the similarity
threshold, merge duplicate signals (take max score, mark source 'both'),
group by category, sort by score DESC. Tests marked integration.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
recompute_for_tag (mean of member embeddings, eligible-kind + min-refs
gated, upsert), list_drifted (the delta-gate: member-count mismatch OR
missing OR wrong model version), find_similar_tags (pgvector cosine
distance, similarity = 1 - distance). Tests marked integration.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
resolve() / resolve_many() (batch, used by the suggestion read path),
idempotent create, remove, list_all. Category-scoped so 'naruto' as
character vs copyright map to different canonicals. Tests marked
integration (real DB).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>