import Link from "next/link"; import { apiSafe, type Forecast, type Meta, type Party, type Poll, type RidingListItem, type SignalsPayload } from "@/lib/api"; import { Battlegrounds, ComponentsCompare, HeroForecast, LatestPolls, MajorityPanel, PopularVoteTable, RegionalGrid, SeatProjectionTable, WhatChanged } from "@/components/forecast-blocks"; import { MapSection } from "@/components/map-section"; import { Legend, SeatMeter } from "@/components/charts"; import { TrendChartInteractive } from "@/components/trend-chart"; import { MarketsPanel, MediaPanel, SignalsOverview } from "@/components/signals"; import { PartyLogo } from "@/components/party"; import { Card, Empty, SectionHeader } from "@/components/ui"; import { PARTY_IDS, partyVar } from "@/lib/parties"; import { int, prob } from "@/lib/format"; import { LiveHome } from "@/components/live"; export const revalidate = 60; export default async function Home() { const [meta, f, polls, ridings, regions, parties, sig] = await Promise.all([ apiSafe("/api/meta"), apiSafe("/api/forecast/latest"), apiSafe<{ polls: Poll[] }>("/api/polls?limit=8"), apiSafe<{ ridings: RidingListItem[] }>("/api/ridings"), apiSafe<{ regions: { id: string; name: string; ridings: number; favorites: Record; voteNow: Record; vote2022: Record }[] }>("/api/regions"), apiSafe<{ parties: Party[] }>("/api/parties"), apiSafe("/api/signals"), ]); if (!meta || !f) return Les projections seront de retour dans quelques instants.; if (meta.mode !== "forecast") return ; const ridingNames = Object.fromEntries((ridings?.ridings ?? []).map((r) => [r.code, { name: r.name, slug: r.slug }])); const seats = Object.fromEntries(Object.entries(f.parties).map(([k, v]) => [k, v.seatsMedian])); const from = new Date(); from.setDate(from.getDate() - 182); const pollPts = (polls?.polls ?? []).map((p) => ({ date: p.fieldworkEnd, values: p.results, pollster: p.pollster.name })); const comp = f.summary.components; return (
{comp && (
Tous les signaux →} />
)}
Prévision complète →} />
Explorer la carte →} />
Tous les sondages →} />
{f.trend && }
{polls && }
{sig && sig.markets.asOf && (
Détail →} />
Revue de presse →} />
)}
{ridings && (
Toutes les circonscriptions →} />
)} {regions && (
)}
13 · Boussole électorale

Quel parti correspond le mieux à vos positions ?

48 énoncés sur 6 axes. Chaque position de parti est tirée d'un document officiel, avec page et citation : vous pourrez voir pourquoi.

Commencer la boussole →
Comparateur →} />
{(parties?.parties ?? []).sort((a, b) => (b.forecast?.seatsMedian ?? 0) - (a.forecast?.seatsMedian ?? 0)).map((p) => (
{p.officialName}
{p.leader} · {p.candidatesCount} candidatures · {p.proposals} propositions documentées
{p.forecast?.seatsMedian ?? "—"}
P(maj) {prob(p.forecast?.pMajority)}
))}
15 · Méthodologie

Comment QC26 calcule

Sondages pondérés et corrigés des effets maison → état latent du vote (filtre de Kalman) → prior fondamentaux et momentum bornés → swing proportionnel sur les résultats 2022 transposés → {int(f.nSimulations)} simulations à erreurs corrélées, repondérées vers les marchés. Chaque run est versionné (modèle {f.modelVersion}, run #{f.id}{f.codeCommit ? `, commit ${f.codeCommit}` : ""}).

MéthodologieChangelog
); }