feat(fc2b): add allowlist-apply + centroid recompute tasks + beat
apply_allowlist_tags: 4 modes (tag-only / image-only / both / full sweep), matches a tag to a prediction either by direct name or via alias (name, category) resolution, gates on per-tag min_confidence, skips applied/rejected, applies source='ml_auto'. recompute_centroid / recompute_centroids: async-bridged calls into CentroidService, delta-gated. Beat: daily backfill, daily centroid recompute, daily allowlist sweep. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -52,3 +52,71 @@ async def test_backfill_enqueues_missing(db, monkeypatch):
|
||||
count = ml_tasks.backfill()
|
||||
assert count >= 1
|
||||
assert img.id in calls
|
||||
|
||||
|
||||
@pytest.mark.integration
|
||||
@pytest.mark.asyncio
|
||||
async def test_apply_allowlist_applies_above_threshold(db):
|
||||
from sqlalchemy import select
|
||||
|
||||
from backend.app.models import ImageRecord, TagAllowlist, TagKind
|
||||
from backend.app.models.tag import image_tag
|
||||
from backend.app.services.tag_service import TagService
|
||||
from backend.app.tasks import ml as ml_tasks
|
||||
|
||||
tag = await TagService(db).find_or_create("autohero", TagKind.character)
|
||||
db.add(TagAllowlist(tag_id=tag.id, min_confidence=0.95))
|
||||
img = ImageRecord(
|
||||
path="/images/al.jpg", sha256="al" + "0" * 62, size_bytes=1,
|
||||
mime="image/jpeg", width=1, height=1,
|
||||
origin="imported_filesystem", integrity_status="unknown",
|
||||
tagger_predictions={
|
||||
"autohero": {"category": "character", "confidence": 0.97}
|
||||
},
|
||||
)
|
||||
db.add(img)
|
||||
await db.commit()
|
||||
|
||||
n = ml_tasks.apply_allowlist_tags(tag_id=tag.id)
|
||||
assert n >= 1
|
||||
src = (
|
||||
await db.execute(
|
||||
select(image_tag.c.source)
|
||||
.where(image_tag.c.image_record_id == img.id)
|
||||
.where(image_tag.c.tag_id == tag.id)
|
||||
)
|
||||
).scalar_one()
|
||||
assert src == "ml_auto"
|
||||
|
||||
|
||||
@pytest.mark.integration
|
||||
@pytest.mark.asyncio
|
||||
async def test_apply_allowlist_skips_below_threshold(db):
|
||||
from sqlalchemy import select
|
||||
|
||||
from backend.app.models import ImageRecord, TagAllowlist, TagKind
|
||||
from backend.app.models.tag import image_tag
|
||||
from backend.app.services.tag_service import TagService
|
||||
from backend.app.tasks import ml as ml_tasks
|
||||
|
||||
tag = await TagService(db).find_or_create("lowconf", TagKind.character)
|
||||
db.add(TagAllowlist(tag_id=tag.id, min_confidence=0.95))
|
||||
img = ImageRecord(
|
||||
path="/images/lc.jpg", sha256="lc" + "0" * 62, size_bytes=1,
|
||||
mime="image/jpeg", width=1, height=1,
|
||||
origin="imported_filesystem", integrity_status="unknown",
|
||||
tagger_predictions={
|
||||
"lowconf": {"category": "character", "confidence": 0.40}
|
||||
},
|
||||
)
|
||||
db.add(img)
|
||||
await db.commit()
|
||||
ml_tasks.apply_allowlist_tags(tag_id=tag.id)
|
||||
applied = (
|
||||
await db.execute(
|
||||
select(image_tag.c.tag_id)
|
||||
.where(image_tag.c.image_record_id == img.id)
|
||||
.where(image_tag.c.tag_id == tag.id)
|
||||
)
|
||||
).scalar_one_or_none()
|
||||
assert applied is None
|
||||
|
||||
Reference in New Issue
Block a user