Source: compute/electionSequence/electionNonPartisanPrimary.js

/** @module */

import electionPhase from '@paretoman/votekit-election-phase'
import { range } from '@paretoman/votekit-utilities'
import getGeometryForPhase from './getGeometryForPhase.js'
import getElectionOptions from './getElectionOptions.js'

/**
 * Here we are in the context of an election sequence with two phases, a non-partisan primary, and a general.
 * @param {*} geometry
 * @param {*} optionsBag
 * @returns {*} sequenceResults
 */
export default function electionNonpartisanPrimary(geometry, optionsBag) {
    // primary phase
    const primaryGeometry = getGeometryForPhase('nonpartisanOpenPrimary', geometry)

    const allCanLabels = range(geometry.canPoints.length)
    primaryGeometry.canLabels = allCanLabels

    const primaryOptions = getElectionOptions('nonpartisanOpenPrimary', 'nonpartisanOpenPrimary', optionsBag)
    const primary = electionPhase(primaryGeometry, primaryOptions, optionsBag)

    // general phase
    const { generalGeometry, primaryWinners } = getGeneralGeometry(geometry, primary)
    const generalOptions = getElectionOptions('nonpartisanOpenPrimary', 'general', optionsBag)
    const general = electionPhase(generalGeometry, generalOptions, optionsBag)

    // combine primary and general results
    const results = combinePrimaryGeneral(primary, general, primaryWinners, geometry, optionsBag)

    return results
}
/** Get the winners of the primary to be candidates in the general. */
function getGeneralGeometry(geometry, primary) {
    const g0 = getGeometryForPhase('general', geometry)
    const generalGeometry = { ...g0 }

    const { allocation } = primary.socialChoiceResults
    const primaryWinners = getWinnerList(allocation)
    generalGeometry.canPoints = primaryWinners.map((iWinner) => g0.canPoints[iWinner])
    generalGeometry.canLabels = primaryWinners

    generalGeometry.parties = { ...g0.parties }
    generalGeometry.parties.partiesByCan = primaryWinners.map((iWinner) => g0.parties.partiesByCan[iWinner])

    return { generalGeometry, primaryWinners }
}

function combinePrimaryGeneral(primary, general, primaryWinners, geometry, optionsBag) {
    const generalAllocation = general.socialChoiceResults.allocation
    const generalWinnerList = getWinnerList(generalAllocation)
    const iWinners = generalWinnerList.map((i) => primaryWinners[i])

    const numCans = geometry.canPoints.length
    const allocation = Array(numCans).fill(0)
    iWinners.forEach((iWinner) => {
        allocation[iWinner] = 1
    })

    const results = {
        phases: {
            nonpartisanOpenPrimary: primary,
            general,
        },
        phaseNames: [
            'nonpartisanOpenPrimary',
            'general',
        ],
        geometry,
        optionsBag,
        socialChoiceResults: {
            allocation,
        },
    }

    return results
}

function getWinnerList(allocation) {
    const iWinners = []
    allocation.forEach((winner, i) => {
        if (winner) {
            iWinners.push(i)
        }
    })
    return iWinners
}