SPB Git

spb/trouve-ka Public

Trouve-KA — moteur de recherche web indépendant, Québec-first. Crawler distribué, index OpenSearch, ranking bilingue, galerie d'images. En prod : www.trouve-ka.com

Python 76.8% TypeScript 15.7% SQL 3.9% Shell 1.4% CSS 1.3% Dockerfile 0.7%
2.8 KB · 83 lines python
Raw Blame History
1# Trouve-KA — worker d'enrichissement2# Author: Simon-Pierre Boucher3# Contact: contact@spboucher.ai45"""Worker d'enrichissement : consomme trouveka:enrich (Redis Streams).67Étape 2 (implémentée) : propage domain_quebec_score et authority_score à jour8dans le document indexé (le domaine apprend au fil du crawl, les documents déjà9indexés en profitent rétroactivement).10Étapes futures : embeddings, entités, classification thématique — même canal,11même contrat : mise à jour partielle du document, jamais bloquante.12"""1314import asyncio15import os16import signal17import uuid1819from trouveka.config import get_settings20from trouveka.database import Database21from trouveka.logging import get_logger22from trouveka.queue import Coordination23from trouveka.search_core import SearchCore2425log = get_logger("enrichment")2627GROUP = "enrichers"282930class EnrichmentWorker:31    def __init__(self) -> None:32        self.s = get_settings()33        self.consumer = f"enrich-{os.getpid()}-{uuid.uuid4().hex[:6]}"34        self.db = Database(self.s.database_url, pool_min=1, pool_max=3)35        self.coord = Coordination(self.s.redis_url)36        self.search = SearchCore(self.s.search_url, self.s.search_index)37        self.stop_event = asyncio.Event()3839    async def enrich(self, payload: dict) -> None:40        domain_row = await self.db.get_domain_by_id(int(payload["domain_id"]))41        if not domain_row:42            return43        await self.search.update_document(44            payload["url"],45            {46                "domain_quebec_score": round(float(domain_row["quebec_score"]), 4),47                "authority_score": round(float(domain_row["authority_score"]), 4),48            },49        )5051    async def start(self) -> None:52        await self.db.connect()53        loop = asyncio.get_running_loop()54        for sig in (signal.SIGINT, signal.SIGTERM):55            loop.add_signal_handler(sig, self.stop_event.set)56        log.info("worker d'enrichissement démarré", extra={"ctx": {"consumer": self.consumer}})5758        while not self.stop_event.is_set():59            try:60                messages = await self.coord.read_enrichment(GROUP, self.consumer, count=20, block_ms=5000)61                for msg_id, payload in messages:62                    try:63                        await self.enrich(payload)64                    except Exception:65                        log.exception("échec enrichissement", extra={"ctx": payload})66                    finally:67                        await self.coord.ack_enrichment(GROUP, msg_id)68            except Exception:69                log.exception("erreur boucle enrichissement (on continue)")70                await asyncio.sleep(2)7172        await self.search.close()73        await self.coord.close()74        await self.db.close()757677def main() -> None:78    asyncio.run(EnrichmentWorker().start())798081if __name__ == "__main__":82    main()83