SPB Git

spb/qc-election

Public
4.8 KB · 108 lines html
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="stylesheet" href="/assets/style.css">15</head>16<body>17<header class="site"></header>18<div class="wrap">19  <div class="hero">20    <h1>Explorateur de sondages</h1>21    <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>22  </div>2324  <div class="filters">25    <label>Du</label><input type="date" id="f-from">26    <label>au</label><input type="date" id="f-to">27    <label>Maison</label><select id="f-pollster"><option value="">Toutes</option></select>28    <label>Affichage</label>29    <select id="f-view">30      <option value="raw">Valeurs brutes</option>31      <option value="adjusted">Valeurs ajustées</option>32    </select>33    <span id="count" style="color:var(--muted);font-size:.82rem"></span>34  </div>3536  <div class="card" style="overflow-x:auto">37    <table class="data" id="polls-table"></table>38  </div>3940  <h2 class="section">Cotes des maisons de sondage</h2>41  <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>42  <div class="card" style="overflow-x:auto"><table class="data" id="pollsters-table"></table></div>43</div>44<footer class="site"></footer>4546<script src="/assets/app.js"></script>47<script>48(async () => {49  const meta = await initShell("/sondages");50  const parties = Object.keys(meta.parties).filter(p => p !== "AUT");51  let polls = await getJSON(`${API}/polls?limit=2000`);5253  const names = [...new Set(polls.map(p => p.pollster))].sort();54  document.getElementById("f-pollster").innerHTML +=55    names.map(n => `<option>${n}</option>`).join("");5657  function render() {58    const from = document.getElementById("f-from").value;59    const to = document.getElementById("f-to").value;60    const ps = document.getElementById("f-pollster").value;61    const view = document.getElementById("f-view").value;62    const rows = polls.filter(x =>63      (!from || x.field_end >= from) && (!to || x.field_end <= to) &&64      (!ps || x.pollster === ps));65    document.getElementById("count").textContent = `${rows.length} sondages`;66    document.getElementById("polls-table").innerHTML =67      `<tr><th>Maison</th><th>Terrain</th><th>n</th><th>MoE</th><th>Population</th>68       ${parties.map(p => `<th>${p}</th>`).join("")}<th>Poids modèle</th><th>Source</th></tr>` +69      rows.map(x => {70        const vals = view === "adjusted" ? x.adjusted : x.raw;71        return `<tr style="${x.excluded ? "opacity:.4" : ""}">72          <td>${x.pollster}${x.excluded ? ' <span class="chip badge-aux">exclu</span>' : ""}</td>73          <td>${fmtDate(x.field_end)}</td>74          <td>${x.sample_size ?? "—"}</td>75          <td>${x.moe ? "±" + x.moe : "—"}</td>76          <td>${x.population}</td>77          ${parties.map(p => `<td>${vals[p] != null ? (+vals[p]).toFixed(1) : "—"}</td>`).join("")}78          <td><div style="background:var(--card-2);border-radius:4px;height:8px;width:70px">79            <div style="background:var(--accent);height:8px;border-radius:4px;width:${(x.weight*100).toFixed(0)}%"></div></div></td>80          <td>${x.source_url ? `<a href="${x.source_url}" target="_blank" rel="noopener">${x.source_name || "lien"}</a>` : (x.source_name || "—")}</td>81        </tr>`;82      }).join("");83  }84  ["f-from", "f-to", "f-pollster", "f-view"].forEach(id =>85    document.getElementById(id).onchange = render);86  render();8788  const raters = await getJSON(`${API}/pollsters`);89  document.getElementById("pollsters-table").innerHTML =90    `<tr><th>Maison</th><th>Sondages en base</th><th>Sondages finaux évalués</th><th>EAM (pp)</th>91     ${parties.map(p => `<th>Biais ${p}</th>`).join("")}<th>Multiplicateur</th></tr>` +92    raters.map(r => {93      const rt = r.rating;94      const he = rt ? rt.house_effects : {};95      return `<tr><td>${r.name}</td><td>${r.polls_in_db}</td>96        <td>${rt ? rt.n_final_polls : "—"}</td>97        <td>${rt && rt.mae_pp != null ? rt.mae_pp : "—"}</td>98        ${parties.map(p => {99          const v = he[p];100          return `<td style="color:${v > 0.5 ? "var(--warn)" : v < -0.5 ? "var(--accent)" : "var(--muted)"}">${v != null ? (v > 0 ? "+" : "") + v.toFixed(1) : "—"}</td>`;101        }).join("")}102        <td>${rt ? rt.weight_multiplier.toFixed(2) : "0.90 (sans historique)"}</td></tr>`;103    }).join("");104})();105</script>106</body>107</html>108
Files · e6325299f4fddaa71fa341f4d44ed851fc90f9ec
Menu

Theme