SPB Git forge

spb/qc-election

Public
20commits 1branches 0releases
4.9 MBsize
maindefault branch
20 days agolast push
Python 66.6% HTML 24.8% CSS 4.9% JavaScript 3.6%
2.5 KB · 58 lines python
Raw Blame History
1# QC Élection Forecast — Plateforme de prévision électorale du Québec 20262# Auteur : Simon-Pierre Boucher3# Contact : contact@spboucher.ai4# https://www.qc-election.com5"""Volatilité de campagne pilotée par les signaux web (v3 « 127 », §16).67Fusionne les signaux d'ATTENTION (jamais directionnels) en un seul8multiplicateur de variance de dérive, STRICTEMENT borné :910    m = 1 + a·max(0, z_wiki − 2) + b·max(0, z_social − 2) + c·confirmation11    1.00 ≤ m ≤ volatility_multiplier_max (défaut 1.5)1213Un pic d'attention (Wikipédia ou médias sociaux) signale un moment chaud de14campagne : l'issue est plus incertaine, dans un sens comme dans l'autre.15La moyenne du forecast n'est JAMAIS touchée. Débrayable par16`enable_social_volatility` (le repli conserve la seule turbulence Wikipédia17de la v2.1, elle-même débrayable)."""18from __future__ import annotations1920import numpy as np21from sqlalchemy.orm import Session2223from ...config import settings2425A_WIKI = 0.10       # pente par unité de z au-delà de 2 (Wikipédia)26B_SOCIAL = 0.06     # pente sociale (plus bruitée → plus faible)27C_CONFIRM = 0.05    # bonus si les DEUX familles de signaux confirment282930def compute(db: Session, wiki_signal: dict | None = None) -> dict:31    """Multiplicateur de variance borné + décomposition publiée."""32    cap = settings.volatility_multiplier_max33    wiki_z = float((wiki_signal or {}).get("z_max") or 0.0)34    social_z, social_avail = 0.0, False35    if settings.enable_social_volatility:36        try:37            from .social import pulse_index38            pulse = pulse_index(db)39            if pulse.get("available"):40                social_avail = True41                social_z = max((v["attention_z"]42                                for v in pulse["parties"].values()), default=0.0)43        except Exception:44            pass45    w_part = A_WIKI * max(0.0, wiki_z - 2.0)46    s_part = B_SOCIAL * max(0.0, social_z - 2.0)47    confirm = C_CONFIRM if (w_part > 0 and s_part > 0) else 0.048    mult = float(np.clip(1.0 + w_part + s_part + confirm, 1.0, cap))49    return {"multiplier": round(mult, 3), "cap": cap,50            "wiki_z": round(wiki_z, 2),51            "social_z": round(social_z, 2) if social_avail else None,52            "components": {"wikipedia": round(w_part, 3),53                           "social": round(s_part, 3),54                           "confirmation": confirm},55            "note": ("Les signaux d'attention n'agissent que sur la VARIANCE "56                     "de dérive (borne stricte ≤ %.2f) — jamais sur la moyenne."57                     % cap)}58