feat(fc2b): add Camie tagger ONNX wrapper

CPU-only, lazy-loaded, process-singleton ONNX session. Parses Camie's
string-category selected_tags.csv (vs WD14's integer Danbooru ids).
STORE_FLOOR (0.05) keeps the stored predictions JSON compact;
SURFACED_CATEGORIES gates which categories the suggestion UI shows
(meta/rating/year stored but never surfaced).

Inference itself isn't unit-tested (1GB model not in CI); the missing-
model error path and pure-logic surface are. Full inference runs in the
local integration suite.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-15 07:35:58 -04:00
parent 906804140c
commit 41fa26ed95
3 changed files with 179 additions and 0 deletions
+41
View File
@@ -0,0 +1,41 @@
"""Tagger unit tests. The ONNX model isn't available in CI (it's a 1GB
download into /models), so these test the pure-logic surface: STORE_FLOOR
constant, SURFACED_CATEGORIES set, TagPrediction dataclass, and the
load()-missing-file error path. Full inference is exercised by the local
integration suite against a real /models volume.
"""
import pytest
from backend.app.services.ml.tagger import (
STORE_FLOOR,
SURFACED_CATEGORIES,
TagPrediction,
Tagger,
get_tagger,
)
def test_surfaced_categories():
assert SURFACED_CATEGORIES == {"artist", "character", "copyright", "general"}
def test_store_floor_is_low():
assert 0 < STORE_FLOOR < 0.2
def test_tag_prediction_dataclass():
p = TagPrediction(name="x", category="general", confidence=0.9)
assert p.name == "x"
assert p.category == "general"
assert p.confidence == 0.9
def test_get_tagger_singleton():
assert get_tagger() is get_tagger()
def test_load_raises_when_model_missing(tmp_path):
t = Tagger(model_dir=tmp_path / "nonexistent")
with pytest.raises(RuntimeError, match="model.onnx missing"):
t.load()