diff --git a/backend/app/services/ml/centroids.py b/backend/app/services/ml/centroids.py index b4b4f77..6a21d77 100644 --- a/backend/app/services/ml/centroids.py +++ b/backend/app/services/ml/centroids.py @@ -23,7 +23,6 @@ from .embedder import MODEL_VERSION as SIGLIP_VERSION ELIGIBLE_KINDS = { TagKind.character, - TagKind.artist, TagKind.fandom, TagKind.general, TagKind.series, diff --git a/backend/app/services/ml/suggestions.py b/backend/app/services/ml/suggestions.py index 2c9ccae..0b8d805 100644 --- a/backend/app/services/ml/suggestions.py +++ b/backend/app/services/ml/suggestions.py @@ -48,12 +48,13 @@ class SuggestionService: ).scalar_one() def _threshold_for(self, s: MLSettings, category: str) -> float: + # 'artist' intentionally absent (FC-2d-vii-c) — falls through to + # the 1.01 "never surfaces" default like any unsurfaced category. return { - "artist": s.suggestion_threshold_artist, "character": s.suggestion_threshold_character, "copyright": s.suggestion_threshold_copyright, "general": s.suggestion_threshold_general, - }.get(category, 1.01) # 1.01 => never surfaces (unsurfaced category) + }.get(category, 1.01) async def for_image(self, image_id: int) -> SuggestionList: img = await self.session.get(ImageRecord, image_id) diff --git a/backend/app/services/ml/tagger.py b/backend/app/services/ml/tagger.py index 40ad21c..92de3a3 100644 --- a/backend/app/services/ml/tagger.py +++ b/backend/app/services/ml/tagger.py @@ -34,7 +34,10 @@ STORE_FLOOR = float(os.environ.get("TAGGER_STORE_FLOOR", "0.05")) # The categories FC-2b surfaces in the UI. Others (meta/rating/year) are # still stored but the suggestion service filters them out. -SURFACED_CATEGORIES = {"artist", "character", "copyright", "general"} +# FC-2d-vii-c: 'artist' retired — artist identity is acquisition-derived +# (image_record.artist_id), never ML-inferred. Raw predictions are still +# stored at STORE_FLOOR but artist never surfaces. +SURFACED_CATEGORIES = {"character", "copyright", "general"} @dataclass(frozen=True) diff --git a/tests/test_ml_artist_retired.py b/tests/test_ml_artist_retired.py new file mode 100644 index 0000000..3bac405 --- /dev/null +++ b/tests/test_ml_artist_retired.py @@ -0,0 +1,27 @@ +import pytest + +pytestmark = pytest.mark.integration + + +def test_artist_not_surfaced(): + from backend.app.services.ml.tagger import SURFACED_CATEGORIES + assert "artist" not in SURFACED_CATEGORIES + + +def test_artist_not_centroid_eligible(): + from backend.app.models import TagKind + from backend.app.services.ml.centroids import ELIGIBLE_KINDS + assert TagKind.artist not in ELIGIBLE_KINDS + + +def test_threshold_for_artist_is_unsurfaced(): + from backend.app.services.ml.suggestions import SuggestionService + + class _S: + suggestion_threshold_character = 0.5 + suggestion_threshold_copyright = 0.5 + suggestion_threshold_general = 0.5 + + svc = SuggestionService.__new__(SuggestionService) + # 'artist' must fall through to the 1.01 "never surfaces" default + assert svc._threshold_for(_S(), "artist") == 1.01