/**
* Federal seat data utilities for the Canada Visualizer
*
* This module provides utilities for processing seat data from the GraphQL API.
* Data is fetched dynamically from Neo4j to ensure accuracy.
*/
export interface ProvinceSeatData {
provinceCode: string;
seats: Record<string, number>;
totalSeats: number;
dominantParty: string;
}
export interface ProvinceSeatCount {
province: string;
party: string;
seats: number;
}
export interface PartySeatTotal {
party: string;
seats: number;
}
// Map full province names (from Neo4j) to province codes (for the map)
const provinceNameToCode: Record<string, string> = {
'British Columbia': 'BC',
'Alberta': 'AB',
'Saskatchewan': 'SK',
'Manitoba': 'MB',
'Ontario': 'ON',
'Quebec': 'QC',
'Québec': 'QC',
'New Brunswick': 'NB',
'Nova Scotia': 'NS',
'Prince Edward Island': 'PE',
'Newfoundland and Labrador': 'NL',
'Yukon': 'YT',
'Northwest Territories': 'NT',
'Nunavut': 'NU',
};
// Party name normalization (API names to display names)
const partyNameMap: Record<string, string> = {
'Liberal': 'Liberal',
'Conservative': 'Conservative',
'NDP': 'NDP',
'Bloc': 'Bloc Québécois',
'Bloc Québécois': 'Bloc Québécois',
'Green Party': 'Green',
'Green': 'Green',
'Independent': 'Independent',
};
/**
* Convert API seat data to the format used by the visualizer
*/
export function processApiSeatData(
apiData: ProvinceSeatCount[]
): Record<string, ProvinceSeatData> {
const result: Record<string, ProvinceSeatData> = {};
// Group by province
for (const item of apiData) {
const provinceCode = provinceNameToCode[item.province];
if (!provinceCode) {
console.warn(`Unknown province: ${item.province}`);
continue;
}
if (!result[provinceCode]) {
result[provinceCode] = {
provinceCode,
seats: {},
totalSeats: 0,
dominantParty: '',
};
}
const partyName = partyNameMap[item.party] || item.party;
result[provinceCode].seats[partyName] = item.seats;
result[provinceCode].totalSeats += item.seats;
}
// Calculate dominant party for each province
for (const provinceCode of Object.keys(result)) {
const province = result[provinceCode];
let maxSeats = 0;
let dominantParty = 'Independent';
for (const [party, seats] of Object.entries(province.seats)) {
if (seats > maxSeats) {
maxSeats = seats;
dominantParty = party;
}
}
province.dominantParty = dominantParty;
}
return result;
}
/**
* Calculate national totals from province seat data
*/
export function calculateNationalTotals(
seatData: Record<string, ProvinceSeatData>
): Record<string, number> {
const totals: Record<string, number> = {};
for (const province of Object.values(seatData)) {
for (const [party, seats] of Object.entries(province.seats)) {
totals[party] = (totals[party] || 0) + seats;
}
}
return totals;
}
/**
* Get seat breakdown for a province
*/
export function getSeatBreakdown(
seatData: Record<string, ProvinceSeatData>,
provinceCode: string
): { party: string; seats: number }[] {
const data = seatData[provinceCode];
if (!data) return [];
return Object.entries(data.seats)
.filter(([_, seats]) => seats > 0)
.map(([party, seats]) => ({ party, seats }))
.sort((a, b) => b.seats - a.seats);
}
/**
* Get dominant party for a province
*/
export function getDominantParty(
seatData: Record<string, ProvinceSeatData>,
provinceCode: string
): string {
return seatData[provinceCode]?.dominantParty ?? 'Independent';
}
/**
* Get province seat data
*/
export function getProvinceSeatData(
seatData: Record<string, ProvinceSeatData>,
provinceCode: string
): ProvinceSeatData | undefined {
return seatData[provinceCode];
}
/**
* Get total seats across all provinces
*/
export function getTotalSeats(
seatData: Record<string, ProvinceSeatData>
): number {
return Object.values(seatData).reduce((sum, p) => sum + p.totalSeats, 0);
}