"""Training hygiene (#128): system-tagged images are ABSENT from other concepts' training data and from CCIP reference prototypes. sklearn only exists in the ml image, so these pin head_training_ids (the sklearn-free selection split out of train_head) rather than a full fit — the exclusion lives entirely in that selection. """ import pytest from sqlalchemy import insert, select from backend.app.models import ImageRecord, ImageRegion, Tag, TagKind from backend.app.models.tag import image_tag from backend.app.services.ml import ccip from backend.app.services.ml.heads import head_training_ids from backend.app.services.ml.training_data import _hygiene_excluded_ids from backend.app.services.tag_service import TagService pytestmark = pytest.mark.integration _CFG = {"min_positives": 2, "neg_ratio": 1} async def _system_wip(db) -> Tag: return (await db.execute( select(Tag).where(Tag.is_system.is_(True), Tag.name == "wip") )).scalar_one() async def _img(db, sha, *, embedded=True): rec = ImageRecord( path=f"/images/{sha}.jpg", sha256=sha, size_bytes=1, mime="image/jpeg", width=1, height=1, origin="imported_filesystem", integrity_status="unknown", siglip_embedding=([0.1] * 1152 if embedded else None), ) db.add(rec) await db.flush() return rec async def _apply(db, image_id, tag_id): await db.execute(insert(image_tag).values( image_record_id=image_id, tag_id=tag_id, source="manual", )) @pytest.mark.asyncio async def test_hygiene_excluded_ids(db): wip = await _system_wip(db) flagged = await _img(db, "a" * 64) await _img(db, "b" * 64) await _apply(db, flagged.id, wip.id) excluded = await db.run_sync(lambda s: _hygiene_excluded_ids(s)) assert excluded == {flagged.id} @pytest.mark.asyncio async def test_head_selection_drops_hygiene_from_both_sides(db): """A wip-tagged image of concept X is neither a positive NOR a sampled negative for X — it is absent entirely.""" wip = await _system_wip(db) concept = await TagService(db).find_or_create("concept_x", TagKind.general) clean_a = await _img(db, "a" * 64) clean_b = await _img(db, "b" * 64) flagged_pos = await _img(db, "c" * 64) # X + wip: dropped positive negative_pool = await _img(db, "d" * 64) # untagged: legit negative flagged_pool = await _img(db, "e" * 64) # wip only: must not be sampled for img in (clean_a, clean_b, flagged_pos): await _apply(db, img.id, concept.id) await _apply(db, flagged_pos.id, wip.id) await _apply(db, flagged_pool.id, wip.id) ids = await db.run_sync(lambda s: head_training_ids(s, concept.id, _CFG)) assert ids is not None pos_ids, neg_ids = ids assert set(pos_ids) == {clean_a.id, clean_b.id} assert negative_pool.id in neg_ids assert flagged_pos.id not in neg_ids assert flagged_pool.id not in neg_ids @pytest.mark.asyncio async def test_system_tags_own_head_keeps_hygiene_positives(db): """The wip head itself trains ON wip-tagged images — that's what makes auto-flagging work.""" wip = await _system_wip(db) one = await _img(db, "a" * 64) two = await _img(db, "b" * 64) await _img(db, "c" * 64) # negative pool await _apply(db, one.id, wip.id) await _apply(db, two.id, wip.id) ids = await db.run_sync(lambda s: head_training_ids(s, wip.id, _CFG)) assert ids is not None pos_ids, _ = ids assert set(pos_ids) == {one.id, two.id} @pytest.mark.asyncio async def test_ccip_references_skip_hygiene_images(db): """A wip's figure region must never become an identity prototype, even on a single-character image.""" ccip._REF_CACHE.update(sig=None, refs=None) wip = await _system_wip(db) char = await TagService(db).find_or_create("Char A", TagKind.character) clean = await _img(db, "a" * 64) flagged = await _img(db, "b" * 64) for img, slot in ((clean, 0), (flagged, 1)): vec = [0.0] * 768 vec[slot] = 1.0 db.add(ImageRegion( image_record_id=img.id, kind="figure", rx=0.0, ry=0.0, rw=1.0, rh=1.0, ccip_embedding=vec, embedding_version="ccip-test", )) await _apply(db, img.id, char.id) await _apply(db, flagged.id, wip.id) await db.commit() refs = await ccip.character_references(db) vectors = refs.get(char.id, []) assert len(vectors) == 1 assert float(vectors[0][0]) == pytest.approx(1.0) assert float(vectors[0][1]) == pytest.approx(0.0)