import asyncio from sqlalchemy import select from ..database.db import session_pool from ..model.paper import Paper from ..core.keyword_engine.extractor import extract_keywords, keywords_to_string from ..core.logger import SingletonLogger logger = SingletonLogger().get_logger() async def index_paper_background(paper_id: int, title: str, abstract: str) -> None: """FastAPI BackgroundTask: extract keywords via SciBERT ONNX and store in Paper.keywords.""" try: keywords = await asyncio.to_thread(extract_keywords, title, abstract, 10) keywords_str = keywords_to_string(keywords) async with session_pool() as session: result = await session.execute( select(Paper).where(Paper.id == paper_id) ) paper = result.scalar_one_or_none() if paper: paper.keywords = keywords_str await session.commit() logger.info(f"Keywords extracted for paper_id={paper_id}: {keywords_str[:100]}") except Exception as e: logger.warning(f"Keyword extraction failed for paper_id={paper_id}: {e}") async def backfill_missing_keywords() -> int: """One-time startup task: extract keywords for saved papers that have none.""" async with session_pool() as session: result = await session.execute( select(Paper) .where(Paper.keywords.is_(None), Paper.title.isnot(None), Paper.abstract.isnot(None)) ) papers = result.scalars().all() if not papers: logger.info("No papers need keyword backfill") return 0 logger.info(f"Backfilling keywords for {len(papers)} papers") count = 0 for p in papers: try: keywords = await asyncio.to_thread(extract_keywords, p.title, p.abstract, 10) keywords_str = keywords_to_string(keywords) async with session_pool() as session: result = await session.execute(select(Paper).where(Paper.id == p.id)) paper = result.scalar_one_or_none() if paper: paper.keywords = keywords_str await session.commit() count += 1 except Exception as e: logger.warning(f"Keyword backfill failed for paper_id={p.id}: {e}") logger.info(f"Keyword backfill complete: {count}/{len(papers)} papers updated") return count