"""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, }