spb/qc-election
Public
Python 66.6%
HTML 24.8%
CSS 4.9%
JavaScript 3.6%
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