import type { Metadata } from "next"; import Link from "next/link"; import { apiSafe } from "@/lib/api"; import { PartyBadge } from "@/components/party"; import { Card, Empty, SectionHeader } from "@/components/ui"; import { PARTY_IDS, partyVar, partyLabel } from "@/lib/parties"; import { pct, prob } from "@/lib/format"; export const metadata: Metadata = { title: "Modèle vs réalité", description: "Précision de la prévision QC26 face aux résultats : circonscriptions correctes, surprises, tossups, calibration, erreur sur les sièges." }; export const revalidate = 0; interface MvR { runId: number; ridingsCompared: number; correct: number; wrong: number; upsets: { code: number; name: string; slug: string; favorite: string; p: number; leader: string; bureaux: [number, number]; marginPct: number | null; final: boolean }[]; tossups: { correct: number; total: number }; calibration: Record; forecastSeats: Record; liveSeats: Record; seatMAE: number | null } export default async function Page() { const d = await apiSafe("/api/live/model-vs-reality", { revalidate: 0 }); if (!d) return ; const acc = d.ridingsCompared ? d.correct / d.ridingsCompared : null; return (
← Direct

Modèle vs réalité

QC26 assume publiquement ses erreurs. Cette page compare la dernière prévision publiée avant 20 h (run #{d.runId}) aux résultats officiels, au fil de la soirée puis en version finale.

{d.ridingsCompared === 0 ? Les métriques apparaîtront dès que des circonscriptions rapporteront. : ( <>
Circonscriptions correctes
{d.correct} / {d.ridingsCompared}
{acc !== null ? prob(acc) : "—"} · favori QC26 = leader actuel
Surprises
{d.upsets.length}
favori ≥ 70 % battu ou devancé
Tossups corrects
{d.tossups.correct} / {d.tossups.total}
courses classées « serré »
Seat MAE
{d.seatMAE !== null ? d.seatMAE.toFixed(1).replace(".", ",") : "—"}
{d.seatMAE !== null ? "erreur absolue moyenne (sièges, final)" : "disponible au dépouillement complet"}
{PARTY_IDS.map((id) => { const live = (d.liveSeats[id]?.elected ?? 0) + (d.liveSeats[id]?.leading ?? 0); return ; })}
PartiForecast (médiane)ÉlusEn avanceLiveÉcart
{d.forecastSeats[id]}{d.liveSeats[id]?.elected ?? 0}{d.liveSeats[id]?.leading ?? 0}{live}{live - d.forecastSeats[id] > 0 ? "+" : ""}{live - d.forecastSeats[id]}
{Object.entries(d.calibration).map(([b, v]) => (
{b} %
{v.n ? Math.round((100 * v.hits) / v.n) : "—"} %
{v.hits}/{v.n} corrects
))}
{d.upsets.length === 0 ?
Aucune surprise pour l'instant.
: {d.upsets.map((u) => )}
CirconscriptionFavori QC26Probabilité initialeLeader actuelBureauxMarge
{u.name}{prob(u.p)} {u.final && final}{u.bureaux[0]}/{u.bureaux[1]}{pct(u.marginPct)}
}

{partyLabel("pq")} et autres sigles : voir Partis.

)}
); }