feat(ml): soft auto-apply — retract auto-tags now below threshold (milestone 139)
CI / lint (push) Successful in 3s
CI / frontend-build (push) Successful in 19s
CI / backend-lint-and-test (push) Successful in 29s
CI / integration (push) Failing after 3m41s

Daily scheduled_retract_auto_tags re-scores standing auto-applied tags and drops
the ones the model no longer supports:
- retract_auto_applied_heads: per graduated head, re-score its source='head_auto'
  images (bounded — only the images already carrying the auto-tag, not the whole
  library) and remove ones now < auto_apply_threshold.
- retract_auto_applied_ccip: per source='ccip_auto' character tag, max-cosine the
  image's figure vectors vs that character's prototypes; remove ones now below the
  ccip auto-apply threshold.
Both SKIP operator-confirmed tags (TagPositiveConfirmation) and are SILENT — a low
score isn't proof the tag was wrong, so no hard negative is recorded (that's
reserved for an operator removal). No-op unless the relevant auto-apply switch is
on. New daily beat. sklearn-free tests for both paths + the disabled no-op.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CDgx8bQS5YrGRK76v8HUnM
This commit is contained in:
2026-07-06 18:13:37 -04:00
parent cbc3e11a53
commit 3006e84cc0
5 changed files with 320 additions and 1 deletions
+20
View File
@@ -592,3 +592,23 @@ def scheduled_ccip_auto_apply() -> str:
applied += 1
session.commit()
return f"applied={applied}"
@celery.task(
name="backend.app.tasks.ml.scheduled_retract_auto_tags",
soft_time_limit=1800, time_limit=2100,
)
def scheduled_retract_auto_tags() -> str:
"""Soft auto-apply (milestone 139): retract standing head_auto/ccip_auto tags
the model no longer supports (score now below the auto-apply threshold),
skipping operator-confirmed ones. Silent (no hard negative). No-op unless the
respective auto-apply switch is on. Returns 'head=N ccip=M'."""
from ..services.ml.character_prototypes import retract_auto_applied_ccip
from ..services.ml.heads import retract_auto_applied_heads
SessionLocal = _sync_session_factory()
with SessionLocal() as session:
n_head = retract_auto_applied_heads(session)
with SessionLocal() as session:
n_ccip = retract_auto_applied_ccip(session)
return f"head={n_head} ccip={n_ccip}"