/** @module */
import * as types from '@paretoman/votekit-types'
import { range } from '@paretoman/votekit-utilities'
import sainteLague from './sainteLague.js'
import sntv from './sntv.js'
/**
* Run an Open List Proportional Representation method. Call this variant by the name "A".
* Return a list of winning candidates and a list of allocated seats to parties.
* A voter votes for a candidate. Party lists help candidates pool their votes.
* Party lists are allocated seats. The most popular candidates in a party are elected.
* @param {types.typesVotes.votes} votes - The object for vote data.
* @param {types.typesSocialChoice.socialChoiceOptions} socialChoiceOptions - options to specify a social choice function.
* @returns {types.typesSocialChoice.socialChoiceResults} - the results returned from a social choice function.
*/
export default function olprA(votes, socialChoiceOptions) {
// Make a tally for each party.
// TODO: provide these variables in votes
const { voteFractionsByCan } = votes.candidateTallies
const { parties } = votes
const { partiesByCan, numParties } = parties
const numCans = voteFractionsByCan.length
const partyVotes = Array(numParties).fill(0)
const seatLimits = Array(numParties).fill(0)
for (let i = 0; i < numCans; i++) {
// Find which party the candidate belongs to - index of party.
const iParty = partiesByCan[i]
// Add tally to party.
partyVotes[iParty] += voteFractionsByCan[i]
seatLimits[iParty] += 1
}
// Find out how many seats each party gets.
// todo: change method
const socialChoiceOptions3 = { ...socialChoiceOptions }
socialChoiceOptions3.seatLimits = seatLimits
const votes3 = { candidateTallies: { voteFractionsByCan: partyVotes }, numCans }
const partyResults = sainteLague(votes3, socialChoiceOptions3)
const partyAllocation = partyResults.allocation
// Which candidates get the seats?
// Find the highest-scoring candidates.
const allocation = Array(numCans).fill(0)
for (let i = 0; i < numParties; i++) {
// Set inputs for SNTV.
const socialChoiceOptions2 = { seats: partyAllocation[i] }
const cansInParty = range(numCans).filter((k) => partiesByCan[k] === i)
const tfWithinParty = cansInParty.map((k) => voteFractionsByCan[k])
const totalTFInParty = tfWithinParty.reduce((p, c) => p + c, 0)
const fractionTfWithinParty = tfWithinParty.map((x) => x / totalTFInParty)
// Run sntv.
const votes2 = { candidateTallies: { voteFractionsByCan: fractionTfWithinParty }, numCans }
const socialChoiceInParty = sntv(votes2, socialChoiceOptions2)
const allocationInParty = socialChoiceInParty.allocation
// Store sntv results in allocation list for all candidates.
for (let k = 0; k < cansInParty.length; k++) {
const iCan = cansInParty[k]
allocation[iCan] = allocationInParty[k]
}
}
// Todo: consider if there is a tie.
const socialChoiceResults = { allocation }
return socialChoiceResults
}
/** @constant {object} - an object: this function and descriptions of its name, input, and output */
export const olprAMetadata = {
name: 'Open List Proportional Representation',
shortName: 'OLPR A',
functionName: 'olprA',
voteCasterName: 'plurality', // for input
socialChoiceType: 'multiWinner',
elect: olprA,
}