# Trouve-KA — priorités et recrawl adaptatif # Author: Simon-Pierre Boucher # Contact: contact@spboucher.ai """Fonction de priorité du frontier et scheduling de recrawl. P = w_q·Q + w_a·A + w_f·F + w_l·L + w_n·N − w_d·D − w_s·S (CLAUDE.md §5.3) Les poids sont des constantes nommées, destinées à être calibrées par mesures. """ from datetime import timedelta # Poids de la fonction de priorité — à calibrer empiriquement, jamais figés W_QUEBEC = 0.40 W_AUTHORITY = 0.15 W_FRESHNESS = 0.10 W_LINKS = 0.10 W_NOVELTY = 0.15 W_DEPTH = 0.05 W_SPAM = 0.30 def compute_priority( *, domain_quebec_score: float, authority_score: float = 0.0, freshness_hint: float = 0.0, link_signal: float = 0.0, is_new_domain: bool = False, depth: int = 0, spam_signal: float = 0.0, is_seed: bool = False, ) -> float: """Priorité ∈ [0, 1]. Les seeds démarrent au maximum.""" if is_seed: return 1.0 p = ( W_QUEBEC * domain_quebec_score + W_AUTHORITY * min(authority_score, 1.0) + W_FRESHNESS * min(freshness_hint, 1.0) + W_LINKS * min(link_signal, 1.0) + W_NOVELTY * (1.0 if is_new_domain else 0.3) - W_DEPTH * min(depth, 10) / 10.0 - W_SPAM * min(spam_signal, 1.0) ) return max(0.0, min(1.0, round(p, 4))) def next_recrawl_delay( *, changed: bool, previous_delay_hours: float | None, min_hours: float = 1.0, max_hours: float = 24 * 30.0, default_hours: float = 24.0, ) -> timedelta: """Recrawl adaptatif (§5.5) : page inchangée → intervalle ×2; page volatile → intervalle ÷2.""" prev = previous_delay_hours or default_hours hours = max(min_hours, prev / 2.0) if changed else min(max_hours, prev * 2.0) return timedelta(hours=hours) def retry_delay(retries: int) -> timedelta: """Backoff exponentiel plafonné pour les erreurs transitoires.""" return timedelta(minutes=min(15 * (2**retries), 60 * 24))