feat(ml): stricter auto-apply defaults to cut misfires (milestone 139)
CI / lint (push) Successful in 3s
CI / frontend-build (push) Successful in 19s
CI / backend-lint-and-test (push) Successful in 39s
CI / integration (push) Failing after 3m48s

head_auto_apply_precision 0.97→0.98, head_auto_apply_min_positives 30→50,
ccip_auto_apply_threshold 0.92→0.95 (operator-asked). Model defaults change for
fresh installs; migration 0081 bumps the existing singleton row IFF still at the
old default (won't clobber a deliberate operator change). ml_admin bounds already
permit these. Fixed a stale comment in the auto-apply test.

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:08:54 -04:00
parent 2cfbb284d5
commit cbc3e11a53
3 changed files with 61 additions and 4 deletions
@@ -0,0 +1,50 @@
"""stricter auto-apply defaults (milestone 139) — cut graduate/auto-apply misfires
head_auto_apply_precision 0.97→0.98, head_auto_apply_min_positives 30→50,
ccip_auto_apply_threshold 0.92→0.95 (operator-asked 2026-07-06). The model
defaults change for fresh installs; here we bump the existing singleton row IFF
it is still at the previous default, so a deliberate operator change is NOT
clobbered.
Revision ID: 0081
Revises: 0080
Create Date: 2026-07-06
"""
from typing import Sequence, Union
from alembic import op
revision: str = "0081"
down_revision: Union[str, None] = "0080"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
op.execute(
"UPDATE ml_settings SET head_auto_apply_precision = 0.98 "
"WHERE head_auto_apply_precision = 0.97"
)
op.execute(
"UPDATE ml_settings SET head_auto_apply_min_positives = 50 "
"WHERE head_auto_apply_min_positives = 30"
)
op.execute(
"UPDATE ml_settings SET ccip_auto_apply_threshold = 0.95 "
"WHERE ccip_auto_apply_threshold = 0.92"
)
def downgrade() -> None:
op.execute(
"UPDATE ml_settings SET head_auto_apply_precision = 0.97 "
"WHERE head_auto_apply_precision = 0.98"
)
op.execute(
"UPDATE ml_settings SET head_auto_apply_min_positives = 30 "
"WHERE head_auto_apply_min_positives = 50"
)
op.execute(
"UPDATE ml_settings SET ccip_auto_apply_threshold = 0.92 "
"WHERE ccip_auto_apply_threshold = 0.95"
)
+10 -3
View File
@@ -51,7 +51,10 @@ class MLSettings(Base):
Integer, nullable=False, default=8 Integer, nullable=False, default=8
) )
head_auto_apply_precision: Mapped[float] = mapped_column( head_auto_apply_precision: Mapped[float] = mapped_column(
Float, nullable=False, default=0.97 # Stricter graduation bar (was 0.97) to cut auto-apply misfires
# (operator-asked 2026-07-06): a higher precision target → fewer heads
# graduate and those that do get a higher per-head auto_apply_threshold.
Float, nullable=False, default=0.98
) )
# Earned auto-apply (#114). A graduated head fires (tags images without a # Earned auto-apply (#114). A graduated head fires (tags images without a
# human) when this master switch is on AND the head has at least # human) when this master switch is on AND the head has at least
@@ -63,7 +66,9 @@ class MLSettings(Base):
Boolean, nullable=False, default=True Boolean, nullable=False, default=True
) )
head_auto_apply_min_positives: Mapped[int] = mapped_column( head_auto_apply_min_positives: Mapped[int] = mapped_column(
Integer, nullable=False, default=30 # Support floor raised 30→50 (operator-asked 2026-07-06): a head needs
# more human labels before it may fire without a human.
Integer, nullable=False, default=50
) )
# CCIP character-match cosine cut (#114). 0.85 default — the v1 flat 0.75 # CCIP character-match cosine cut (#114). 0.85 default — the v1 flat 0.75
# over-fired (high-reference characters matched a scatter of images); 0.85 # over-fired (high-reference characters matched a scatter of images); 0.85
@@ -78,7 +83,9 @@ class MLSettings(Base):
Boolean, nullable=False, default=True Boolean, nullable=False, default=True
) )
ccip_auto_apply_threshold: Mapped[float] = mapped_column( ccip_auto_apply_threshold: Mapped[float] = mapped_column(
Float, nullable=False, default=0.92 # Raised 0.92→0.95 (operator-asked 2026-07-06) so only very confident
# character matches auto-tag.
Float, nullable=False, default=0.95
) )
# Default = SigLIP 2 (so400m, 512px) for new installs (migration 0069); # Default = SigLIP 2 (so400m, 512px) for new installs (migration 0069);
# existing libraries keep their stored value until the operator re-embeds. # existing libraries keep their stored value until the operator re-embeds.
+1 -1
View File
@@ -88,7 +88,7 @@ def test_sweep_dry_run_counts_but_writes_nothing(db_sync):
def test_sweep_skips_under_supported_head(db_sync): def test_sweep_skips_under_supported_head(db_sync):
# n_pos below head_auto_apply_min_positives (default 30) → a precise-looking # n_pos below head_auto_apply_min_positives (default 50) → a precise-looking
# but under-supported head never fires. # but under-supported head never fires.
img = _img(db_sync, "c" * 64, _emb(0)) img = _img(db_sync, "c" * 64, _emb(0))
tag = Tag(name="weaktag", kind=TagKind.general) tag = Tag(name="weaktag", kind=TagKind.general)