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.6 KB · 68 lines python
Raw Blame History
1# Trouve-KA — construction du document d'index2# Author: Simon-Pierre Boucher3# Contact: contact@spboucher.ai45"""Transforme une page parsée + signaux Québec en document OpenSearch (étape 1)."""67from datetime import UTC, datetime8from typing import Any910from trouveka.types import ParsedPage, QuebecSignals1112# Catégorisation grossière par domaine — raffinée en asynchrone (étape 2)13_GOV_SUFFIXES = (".gouv.qc.ca", ".gc.ca", ".quebec.ca")14_GOV_DOMAINS = {"quebec.ca", "canada.ca", "montreal.ca", "laval.ca", "gatineau.ca", "sherbrooke.ca"}15_NEWS_DOMAINS = {16    "lapresse.ca", "ledevoir.com", "journaldemontreal.com", "journaldequebec.com",17    "radio-canada.ca", "tvanouvelles.ca", "lesoleil.com", "ledroit.com",18    "latribune.ca", "lenouvelliste.ca", "lequotidien.com", "lavoixdelest.ca",19    "noovo.info", "24heures.ca", "montrealgazette.com",20}21_EDU_SUFFIXES = (".ulaval.ca", ".umontreal.ca", ".mcgill.ca", ".uqam.ca", ".usherbrooke.ca",22                 ".concordia.ca", ".polymtl.ca", ".etsmtl.ca", ".hec.ca")232425def categorize_domain(domain: str) -> list[str]:26    d = domain.lower()27    cats: list[str] = []28    if d in _GOV_DOMAINS or any(d.endswith(s) for s in _GOV_SUFFIXES) or ".gouv." in d:29        cats.append("government")30    if d in _NEWS_DOMAINS:31        cats.append("news")32    if any(d.endswith(s) or d == s.lstrip(".") for s in _EDU_SUFFIXES) or d.endswith(".edu"):33        cats.append("education")34    return cats353637def build_search_document(38    page: ParsedPage,39    signals: QuebecSignals,40    *,41    domain: str,42    domain_quebec_score: float,43    authority_score: float = 0.0,44) -> dict[str, Any]:45    return {46        "url": page.url,47        "canonical_url": page.canonical_url or page.url,48        "domain": domain,49        "title": page.title,50        "description": page.description,51        "body": page.body[:100_000],52        "headings": page.headings,53        "language": page.language,54        "page_quebec_score": signals.score,55        "domain_quebec_score": round(domain_quebec_score, 4),56        "locations": signals.locations,57        "organizations": [],   # enrichissement étape 258        "people": [],          # enrichissement étape 259        "categories": categorize_domain(domain),60        "image_url": page.image_url,61        "published_at": page.published_at.isoformat() if page.published_at else None,62        "crawled_at": datetime.now(UTC).isoformat(),63        "authority_score": round(authority_score, 4),64        "freshness_score": 0.0,  # enrichissement étape 365        "quality_score": 0.0,    # enrichissement étape 366        "spam_score": 0.0,       # enrichissement étape 367    }68