diff --git a/alembic/versions/0081_stricter_auto_apply_defaults.py b/alembic/versions/0081_stricter_auto_apply_defaults.py new file mode 100644 index 0000000..0a7f893 --- /dev/null +++ b/alembic/versions/0081_stricter_auto_apply_defaults.py @@ -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" + ) diff --git a/backend/app/models/ml_settings.py b/backend/app/models/ml_settings.py index c03075e..3c60497 100644 --- a/backend/app/models/ml_settings.py +++ b/backend/app/models/ml_settings.py @@ -51,7 +51,10 @@ class MLSettings(Base): Integer, nullable=False, default=8 ) 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 # 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 ) 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 # over-fired (high-reference characters matched a scatter of images); 0.85 @@ -78,7 +83,9 @@ class MLSettings(Base): Boolean, nullable=False, default=True ) 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); # existing libraries keep their stored value until the operator re-embeds. diff --git a/tests/test_head_auto_apply.py b/tests/test_head_auto_apply.py index c289ca0..91314ce 100644 --- a/tests/test_head_auto_apply.py +++ b/tests/test_head_auto_apply.py @@ -88,7 +88,7 @@ def test_sweep_dry_run_counts_but_writes_nothing(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. img = _img(db_sync, "c" * 64, _emb(0)) tag = Tag(name="weaktag", kind=TagKind.general)