# QC Élection Forecast — Plateforme de prévision électorale du Québec 2026 # Auteur : Simon-Pierre Boucher # Contact : contact@spboucher.ai # https://www.qc-election.com """Volatilité de campagne pilotée par les signaux web (v3 « 127 », §16). Fusionne les signaux d'ATTENTION (jamais directionnels) en un seul multiplicateur de variance de dérive, STRICTEMENT borné : m = 1 + a·max(0, z_wiki − 2) + b·max(0, z_social − 2) + c·confirmation 1.00 ≤ m ≤ volatility_multiplier_max (défaut 1.5) Un pic d'attention (Wikipédia ou médias sociaux) signale un moment chaud de campagne : l'issue est plus incertaine, dans un sens comme dans l'autre. La moyenne du forecast n'est JAMAIS touchée. Débrayable par `enable_social_volatility` (le repli conserve la seule turbulence Wikipédia de la v2.1, elle-même débrayable).""" from __future__ import annotations import numpy as np from sqlalchemy.orm import Session from ...config import settings A_WIKI = 0.10 # pente par unité de z au-delà de 2 (Wikipédia) B_SOCIAL = 0.06 # pente sociale (plus bruitée → plus faible) C_CONFIRM = 0.05 # bonus si les DEUX familles de signaux confirment def compute(db: Session, wiki_signal: dict | None = None) -> dict: """Multiplicateur de variance borné + décomposition publiée.""" cap = settings.volatility_multiplier_max wiki_z = float((wiki_signal or {}).get("z_max") or 0.0) social_z, social_avail = 0.0, False if settings.enable_social_volatility: try: from .social import pulse_index pulse = pulse_index(db) if pulse.get("available"): social_avail = True social_z = max((v["attention_z"] for v in pulse["parties"].values()), default=0.0) except Exception: pass w_part = A_WIKI * max(0.0, wiki_z - 2.0) s_part = B_SOCIAL * max(0.0, social_z - 2.0) confirm = C_CONFIRM if (w_part > 0 and s_part > 0) else 0.0 mult = float(np.clip(1.0 + w_part + s_part + confirm, 1.0, cap)) return {"multiplier": round(mult, 3), "cap": cap, "wiki_z": round(wiki_z, 2), "social_z": round(social_z, 2) if social_avail else None, "components": {"wikipedia": round(w_part, 3), "social": round(s_part, 3), "confirmation": confirm}, "note": ("Les signaux d'attention n'agissent que sur la VARIANCE " "de dérive (borne stricte ≤ %.2f) — jamais sur la moyenne." % cap)}