import Link from "next/link";
import type { Forecast, Poll, RidingListItem } from "@/lib/api";
import { cls, dateShort, dateTimeFr, delta, int, pct, prob, ratingLabel } from "@/lib/format";
import { PARTY_IDS, party, partyColor, partyLabel, partyName, sortParties } from "@/lib/parties";
import { SeatDistribution } from "./charts";
import { PartyBadge, PartyDot, PartyLogo } from "./party";
import { Badge, Card, Metric, MetricDelta, ProbabilityBar, SectionHeader, SourceBadge } from "./ui";
export function SeatProjectionTable({ f, className }: { f: Forecast; className?: string }) {
const rows = sortParties(f.parties, (v) => v.seatsMean);
const maj = f.summary.majority;
return (
Parti
Projection
Intervalle 80 %
Intervalle 95 %
Plus de sièges
Majorité ({maj})
Distribution
{rows.map(([id, v]) => (
{partyLabel(id)}
{v.seatsMedian}
{v.seats80[0]}–{v.seats80[1]}
{v.seats95[0]}–{v.seats95[1]}
{prob(v.pMostSeats)}
{prob(v.pMajority)}
{v.seatDistribution && }
))}
Médiane des {int(f.nSimulations)} simulations ; intervalles = percentiles 10–90 et 2,5–97,5. « Autres » n'est jamais déclaré gagnant dans la version 1 du modèle.
Nouveau{(cause.detail as string[]).length > 1 ? "x" : ""} sondage{(cause.detail as string[]).length > 1 ? "s" : ""} : {(cause.detail as string[]).map((id) => {id})}
) :
{String(cause?.detail ?? "—")}
}
Comparé au run précédent{d.previous_at ? ` (${dateTimeFr(d.previous_at)})` : ""}. Les causes listées sont les seules différences d'entrée entre les deux runs ; QC26 n'infère pas de causalité au-delà.
Variations par parti
Parti
Vote
Sièges
P(majorité)
P(1er)
{Object.entries(d.changes).filter(([id]) => id !== "aut").sort((a, b) => Math.abs(b[1].seats) - Math.abs(a[1].seats)).map(([id, c]) => (
Dernière mise à jour {dateTimeFr(f.completedAt)}{f.pollCount} sondages · {int(f.nSimulations)} simulationsDernier terrain {dateShort(f.summary.latest_poll)}
Méthodologie