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%
5.9 KB · 123 lines html
Raw Blame History
1<!DOCTYPE html>2<!--3  QC Élection Forecast — Plateforme de prévision électorale du Québec 20264  Auteur : Simon-Pierre Boucher5  Contact : contact@spboucher.ai6  https://www.qc-election.com7-->8<html lang="fr">9<head>10<script>document.documentElement.dataset.theme=localStorage.getItem("qce-theme")||"dark";</script>11<meta charset="utf-8">12<meta name="viewport" content="width=device-width, initial-scale=1">13<title>Explorateur de sondages — QC Élection Forecast</title>14<link rel="icon" type="image/svg+xml" href="/assets/img/favicon.svg">15<link rel="icon" type="image/png" sizes="64x64" href="/assets/img/favicon-64.png">16<link rel="apple-touch-icon" href="/assets/img/apple-touch-icon.png">17<meta property="og:site_name" content="QC Élection Forecast">18<meta property="og:type" content="website">19<meta property="og:title" content="Explorateur de sondages — QC Élection Forecast">20<meta property="og:description" content="Prévision probabiliste de l'élection générale québécoise du 5 octobre 2026 : 127 circonscriptions, modèle bayésien calibré, sources traçables.">21<meta property="og:image" content="https://www.qc-election.com/assets/img/og.png">22<meta property="og:image:width" content="1200">23<meta property="og:image:height" content="630">24<meta property="og:locale" content="fr_CA">25<meta name="twitter:card" content="summary_large_image">26<meta name="twitter:title" content="Explorateur de sondages — QC Élection Forecast">27<meta name="twitter:image" content="https://www.qc-election.com/assets/img/og.png">28<meta name="theme-color" content="#0a0d14">29<link rel="stylesheet" href="/assets/style.css">30</head>31<body>32<header class="site"></header>33<div class="wrap">34  <div class="hero">35    <h1>Explorateur de sondages</h1>36    <p class="sub">Chaque sondage avec sa valeur brute, sa valeur ajustée (effets maison) et son poids dans le modèle — transparence totale.</p>37  </div>3839  <div class="filters">40    <label>Du</label><input type="date" id="f-from">41    <label>au</label><input type="date" id="f-to">42    <label>Maison</label><select id="f-pollster"><option value="">Toutes</option></select>43    <label>Affichage</label>44    <select id="f-view">45      <option value="raw">Valeurs brutes</option>46      <option value="adjusted">Valeurs ajustées</option>47    </select>48    <span id="count" style="color:var(--muted);font-size:.82rem"></span>49  </div>5051  <div class="card" style="padding:10px 12px">52    <div class="table-scroll"><table class="data" id="polls-table"></table></div>53  </div>5455  <h2 class="section">Cotes des maisons de sondage</h2>56  <p class="section-sub">Effets maison estimés sur les sondages finaux des élections passées (valeur positive = la maison a surestimé le parti), rétrécis de 50 % vers zéro et bornés à ±3 pp.</p>57  <div class="card" style="overflow-x:auto"><table class="data" id="pollsters-table"></table></div>58</div>59<footer class="site"></footer>6061<script src="/assets/app.js"></script>62<script>63(async () => {64  const meta = await initShell("/sondages");65  const parties = Object.keys(meta.parties).filter(p => p !== "AUT");66  let polls = await getJSON(`${API}/polls?limit=2000`);6768  const names = [...new Set(polls.map(p => p.pollster))].sort();69  document.getElementById("f-pollster").innerHTML +=70    names.map(n => `<option>${n}</option>`).join("");7172  function render() {73    const from = document.getElementById("f-from").value;74    const to = document.getElementById("f-to").value;75    const ps = document.getElementById("f-pollster").value;76    const view = document.getElementById("f-view").value;77    const rows = polls.filter(x =>78      (!from || x.field_end >= from) && (!to || x.field_end <= to) &&79      (!ps || x.pollster === ps));80    document.getElementById("count").textContent = `${rows.length} sondages`;81    document.getElementById("polls-table").innerHTML =82      `<tr><th>Maison</th><th>Terrain</th><th>n</th><th>MoE</th><th>Population</th>83       ${parties.map(p => `<th>${p}</th>`).join("")}<th>Poids modèle</th><th>Source</th></tr>` +84      rows.map(x => {85        const vals = view === "adjusted" ? x.adjusted : x.raw;86        return `<tr style="${x.excluded ? "opacity:.4" : ""}">87          <td>${x.pollster}${x.excluded ? ' <span class="chip badge-aux">exclu</span>' : ""}</td>88          <td>${fmtDate(x.field_end)}</td>89          <td>${x.sample_size ?? "—"}</td>90          <td>${x.moe ? "±" + x.moe : "—"}</td>91          <td>${x.population}</td>92          ${parties.map(p => `<td>${vals[p] != null ? (+vals[p]).toFixed(1) : "—"}</td>`).join("")}93          <td><div style="background:var(--card-2);border-radius:4px;height:8px;width:70px">94            <div style="background:var(--accent);height:8px;border-radius:4px;width:${(x.weight*100).toFixed(0)}%"></div></div></td>95          <td>${x.source_url ? `<a href="${x.source_url}" target="_blank" rel="noopener">${x.source_name || "lien"}</a>` : (x.source_name || "—")}</td>96        </tr>`;97      }).join("");98  }99  ["f-from", "f-to", "f-pollster", "f-view"].forEach(id =>100    document.getElementById(id).onchange = render);101  render();102103  const raters = await getJSON(`${API}/pollsters`);104  document.getElementById("pollsters-table").innerHTML =105    `<tr><th>Maison</th><th>Sondages en base</th><th>Sondages finaux évalués</th><th>EAM (pp)</th>106     ${parties.map(p => `<th>Biais ${p}</th>`).join("")}<th>Multiplicateur</th></tr>` +107    raters.map(r => {108      const rt = r.rating;109      const he = rt ? rt.house_effects : {};110      return `<tr><td>${r.name}</td><td>${r.polls_in_db}</td>111        <td>${rt ? rt.n_final_polls : "—"}</td>112        <td>${rt && rt.mae_pp != null ? rt.mae_pp : "—"}</td>113        ${parties.map(p => {114          const v = he[p];115          return `<td style="color:${v > 0.5 ? "var(--warn)" : v < -0.5 ? "var(--accent)" : "var(--muted)"}">${v != null ? (v > 0 ? "+" : "") + v.toFixed(1) : "—"}</td>`;116        }).join("")}117        <td>${rt ? rt.weight_multiplier.toFixed(2) : "0.90 (sans historique)"}</td></tr>`;118    }).join("");119})();120</script>121</body>122</html>123