feat(tags): merge repoints allowlist, embedding, aliases, fandom children

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-16 13:38:27 -04:00
parent 003181ed41
commit 33efba8bf5
2 changed files with 129 additions and 5 deletions
+32 -4
View File
@@ -368,18 +368,46 @@ class TagService:
)
async def _repoint_allowlist(self, src: int, tgt: int) -> None:
pass
tgt_has = await self.session.scalar(
select(exists().where(TagAllowlist.tag_id == tgt))
)
if tgt_has:
await self.session.execute(
text("DELETE FROM tag_allowlist WHERE tag_id = :src"),
{"src": src},
)
else:
await self.session.execute(
update(TagAllowlist)
.where(TagAllowlist.tag_id == src)
.values(tag_id=tgt)
)
async def _repoint_embedding(self, src: int) -> None:
pass
await self.session.execute(
text(
"DELETE FROM tag_reference_embedding WHERE tag_id = :src"
),
{"src": src},
)
async def _repoint_aliases(self, src: int, tgt: int) -> None:
pass
from ..models.tag_alias import TagAlias
await self.session.execute(
update(TagAlias)
.where(TagAlias.canonical_tag_id == src)
.values(canonical_tag_id=tgt)
)
async def _repoint_fandom_children(
self, src: int, tgt: int, src_kind: TagKind
) -> None:
pass
if src_kind != TagKind.fandom:
return
await self.session.execute(
update(Tag).where(Tag.fandom_id == src).values(fandom_id=tgt)
)
async def _create_protective_aliases(
self, src_name: str, src_kind: TagKind, tgt: int
+97 -1
View File
@@ -1,5 +1,5 @@
import pytest
from sqlalchemy import select
from sqlalchemy import func, select
from backend.app.models import Tag, TagKind, image_tag
from backend.app.models.tag_allowlist import TagAllowlist
@@ -206,3 +206,99 @@ async def test_merge_dedups_suggestion_rejections(db):
)
)
).first() is None
@pytest.mark.asyncio
async def test_merge_allowlist_target_has_keeps_target_threshold(db):
svc = TagService(db)
a = await svc.find_or_create("SrcAL", TagKind.general)
b = await svc.find_or_create("TgtAL", TagKind.general)
db.add(TagAllowlist(tag_id=a.id, min_confidence=0.5))
db.add(TagAllowlist(tag_id=b.id, min_confidence=0.9))
await db.flush()
await svc.merge(a.id, b.id)
rows = (await db.execute(select(TagAllowlist))).scalars().all()
assert len(rows) == 1
assert rows[0].tag_id == b.id
assert rows[0].min_confidence == 0.9
@pytest.mark.asyncio
async def test_merge_allowlist_source_only_moves_to_target(db):
svc = TagService(db)
a = await svc.find_or_create("SrcAL2", TagKind.general)
b = await svc.find_or_create("TgtAL2", TagKind.general)
db.add(TagAllowlist(tag_id=a.id, min_confidence=0.42))
await db.flush()
await svc.merge(a.id, b.id)
rows = (await db.execute(select(TagAllowlist))).scalars().all()
assert len(rows) == 1
assert rows[0].tag_id == b.id
assert rows[0].min_confidence == 0.42
@pytest.mark.asyncio
async def test_merge_deletes_source_embedding(db):
from backend.app.models.tag_reference_embedding import (
TagReferenceEmbedding,
)
svc = TagService(db)
a = await svc.find_or_create("SrcEmb", TagKind.general)
b = await svc.find_or_create("TgtEmb", TagKind.general)
db.add(
TagReferenceEmbedding(
tag_id=a.id,
embedding=[0.0] * 1152,
reference_count=1,
model_version="v",
)
)
await db.flush()
await svc.merge(a.id, b.id)
db.expire_all() # merge() uses Core DML; drop stale identity-map state
remaining = await db.scalar(
select(func.count())
.select_from(TagReferenceEmbedding)
.where(TagReferenceEmbedding.tag_id == a.id)
)
assert remaining == 0
@pytest.mark.asyncio
async def test_merge_repoints_existing_aliases(db):
from backend.app.models.tag_alias import TagAlias
svc = TagService(db)
a = await svc.find_or_create("SrcAlias", TagKind.general)
b = await svc.find_or_create("TgtAlias", TagKind.general)
db.add(
TagAlias(
alias_string="oldpred",
alias_category="general",
canonical_tag_id=a.id,
)
)
await db.flush()
await svc.merge(a.id, b.id)
db.expire_all() # merge() uses Core DML; drop stale identity-map state
al = (
await db.execute(
select(TagAlias).where(TagAlias.alias_string == "oldpred")
)
).scalar_one()
assert al.canonical_tag_id == b.id
@pytest.mark.asyncio
async def test_merge_fandom_reparents_children(db):
svc = TagService(db)
f_src = await svc.find_or_create("FandomSrc", TagKind.fandom)
f_tgt = await svc.find_or_create("FandomTgt", TagKind.fandom)
child = await svc.find_or_create(
"ChildChar", TagKind.character, fandom_id=f_src.id
)
await svc.merge(f_src.id, f_tgt.id)
db.expire_all() # merge() uses Core DML; drop stale identity-map state
refreshed = await db.get(Tag, child.id)
assert refreshed.fandom_id == f_tgt.id