"""章节封面是否可入队(与 Celery 任务共享,避免循环 import)。""" from __future__ import annotations from typing import Any from app.features.memoir.memoir_images.schema import ( IMAGE_STATUS_FAILED, IMAGE_STATUS_PENDING, ) def primary_chapter_memoir_image(chapter: Any) -> Any | None: """章节级 MemoirImage(封面槽位):按 order_index 最小取第一条。""" imgs = sorted( getattr(chapter, "images", None) or [], key=lambda m: getattr(m, "order_index", 0), ) return imgs[0] if imgs else None def chapter_needs_cover_enqueue(chapter) -> bool: """尚无 cover_asset 且章节有正文时,可派发 generate_chapter_cover。""" if not chapter: return False if getattr(chapter, "cover_asset_id", None): return False md = (getattr(chapter, "canonical_markdown", None) or "").strip() return bool(md) def chapter_has_cover_to_generate(chapter) -> bool: """章节是否有待生成的封面图(任一条 chapter 级 MemoirImage 为 pending/failed)。""" for m in getattr(chapter, "images", None) or []: status = (getattr(m, "status") or "").strip() if status in (IMAGE_STATUS_PENDING, IMAGE_STATUS_FAILED): return True return False def cover_memoir_image_pending_or_failed(chapter: Any) -> Any | None: """用于补图任务:按 order_index 找到第一条 pending/failed 的章节配图行。""" images = sorted( getattr(chapter, "images", None) or [], key=lambda m: getattr(m, "order_index", 0), ) for m in images: st = (getattr(m, "status") or "").strip() if st in (IMAGE_STATUS_PENDING, IMAGE_STATUS_FAILED): return m return None