0f35a0c484
Consolidated merge of feat/tag-suggestions branch. Original 64-commit history was lost to git-object corruption in a Nextcloud-synced checkout; this single commit captures the equivalent diff. Includes: - pgvector-backed tag suggestion infra (WD14 + SigLIP centroids, ml-worker container, Celery tasks, suggestion service, accept/reject endpoints + modal UI with green/red chip buttons) - Character/fandom integrity: title-case normalization on every write path, fandom-id backfill, maintenance task + settings button, migrations g26041901 + h26041901 to canonicalize legacy rows with case-only duplicate merging - Tag-underscores + modal polish: WD14 name canonicalization at emit + accept + add/bulk-add paths, migration i26041901 for legacy-row rename-or-merge across character/fandom/NULL kinds, suggestion-accept refresh parity via awaited loadTags, persistent chip tint
67 lines
2.0 KiB
Python
67 lines
2.0 KiB
Python
"""Maintenance-queue Celery tasks. Lightweight, user-triggered, non-ML."""
|
|
import logging
|
|
from celery import shared_task
|
|
from sqlalchemy import text
|
|
|
|
from app import db
|
|
from app.models import Tag
|
|
|
|
log = logging.getLogger(__name__)
|
|
|
|
|
|
@shared_task(
|
|
name='app.tasks.maintenance.sync_character_fandoms',
|
|
soft_time_limit=120,
|
|
time_limit=180,
|
|
)
|
|
def sync_character_fandoms():
|
|
"""For every character tag with a non-null fandom_id, attach the fandom
|
|
tag to every image tagged with the character but not the fandom.
|
|
|
|
Additive only — never removes fandom tags.
|
|
|
|
Returns a summary dict with counts.
|
|
"""
|
|
chars = (
|
|
Tag.query
|
|
.filter_by(kind='character')
|
|
.filter(Tag.fandom_id.isnot(None))
|
|
.all()
|
|
)
|
|
total_added = 0
|
|
failed = 0
|
|
for char in chars:
|
|
try:
|
|
result = db.session.execute(
|
|
text("""
|
|
INSERT INTO image_tags (image_id, tag_id)
|
|
SELECT it.image_id, :fandom_id
|
|
FROM image_tags it
|
|
WHERE it.tag_id = :char_id
|
|
AND NOT EXISTS (
|
|
SELECT 1 FROM image_tags it2
|
|
WHERE it2.image_id = it.image_id
|
|
AND it2.tag_id = :fandom_id
|
|
)
|
|
"""),
|
|
{'char_id': char.id, 'fandom_id': char.fandom_id},
|
|
)
|
|
total_added += result.rowcount or 0
|
|
db.session.commit()
|
|
except Exception as e:
|
|
db.session.rollback()
|
|
failed += 1
|
|
log.warning(
|
|
"sync_character_fandoms: char tag %s (%s) failed: %s",
|
|
char.id, char.name, e,
|
|
)
|
|
log.info(
|
|
"sync_character_fandoms: scanned %d characters, added %d image↔fandom links, %d failed",
|
|
len(chars), total_added, failed,
|
|
)
|
|
return {
|
|
'characters_scanned': len(chars),
|
|
'links_added': total_added,
|
|
'characters_failed': failed,
|
|
}
|