From c8b815afe6fe0680ee1298e7ff5a43a9b51e356d Mon Sep 17 00:00:00 2001 From: Bryan Van Deusen Date: Wed, 10 Jun 2026 13:52:20 -0400 Subject: [PATCH] feat(ml): clamp allowlist min_confidence to the tagger store floor MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Consumer #4 of the store-floor change (#764). An allowlist tag can't auto-apply more permissively than the ingest floor — predictions below tagger_store_floor aren't stored, so a lower min_confidence behaves identically to the floor. update_threshold now clamps to max(value, floor); the AllowlistTable confidence input min-binds to the live floor and clamps on edit. Keeps the stored threshold honest about actual apply behavior. Co-Authored-By: Claude Opus 4.8 (1M context) --- backend/app/services/ml/allowlist.py | 17 +++++++++++++++-- .../src/components/settings/AllowlistTable.vue | 17 +++++++++++++---- tests/test_ml_allowlist.py | 14 ++++++++++++++ 3 files changed, 42 insertions(+), 6 deletions(-) diff --git a/backend/app/services/ml/allowlist.py b/backend/app/services/ml/allowlist.py index dc8aeab..08a6ac7 100644 --- a/backend/app/services/ml/allowlist.py +++ b/backend/app/services/ml/allowlist.py @@ -9,7 +9,7 @@ from sqlalchemy import delete, select from sqlalchemy.dialects.postgresql import insert from sqlalchemy.ext.asyncio import AsyncSession -from ...models import Tag, TagAllowlist, TagSuggestionRejection +from ...models import MLSettings, Tag, TagAllowlist, TagSuggestionRejection from ...models.tag import image_tag from .aliases import AliasService @@ -91,12 +91,25 @@ class AllowlistService: ) await self.dismiss(image_id, tag_id) + async def _store_floor(self) -> float: + return ( + await self.session.execute( + select(MLSettings.tagger_store_floor).where(MLSettings.id == 1) + ) + ).scalar_one() + async def update_threshold( self, tag_id: int, min_confidence: float ) -> None: row = await self.session.get(TagAllowlist, tag_id) if row is not None: - row.min_confidence = min_confidence + # An allowlist tag can't auto-apply more permissively than the + # ingest store floor — predictions below tagger_store_floor aren't + # stored, so a lower min_confidence would behave identically to the + # floor. Clamp so the stored threshold matches actual behavior + # (#764). + floor = await self._store_floor() + row.min_confidence = max(min_confidence, floor) async def remove(self, tag_id: int) -> None: await self.session.execute( diff --git a/frontend/src/components/settings/AllowlistTable.vue b/frontend/src/components/settings/AllowlistTable.vue index 9448125..2ac386d 100644 --- a/frontend/src/components/settings/AllowlistTable.vue +++ b/frontend/src/components/settings/AllowlistTable.vue @@ -10,7 +10,7 @@ @@ -25,23 +25,32 @@