Skip to content

Commit

Permalink
Merge pull request #420 from mikefranze/results-types2
Browse files Browse the repository at this point in the history
Results types
  • Loading branch information
mikefranze authored Dec 17, 2023
2 parents 995a762 + bc1be6a commit cc64c92
Show file tree
Hide file tree
Showing 23 changed files with 240 additions and 228 deletions.
12 changes: 8 additions & 4 deletions backend/src/Controllers/getElectionResultsController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { permissions } from '../../../domain_model/permissions';
import { VotingMethods } from '../Tabulators/VotingMethodSelecter';
import { IElectionRequest } from "../IRequest";
import { Response, NextFunction } from 'express';
import { ElectionResults } from "../../../domain_model/ITabulators";
var seedrandom = require('seedrandom');

const BallotModel = ServiceLocator.ballotsDb();
Expand All @@ -28,7 +29,7 @@ const getElectionResults = async (req: IElectionRequest, res: Response, next: Ne
}

const election = req.election
let results = []
let results: ElectionResults[] = []
for (let race_index = 0; race_index < election.races.length; race_index++) {
const candidateNames = election.races[race_index].candidates.map((Candidate: any) => (Candidate.candidate_name))
const race_id = election.races[race_index].race_id
Expand All @@ -51,13 +52,16 @@ const getElectionResults = async (req: IElectionRequest, res: Response, next: Ne
Logger.info(req, msg);
let rng = seedrandom(election.election_id + ballots.length.toString())
const tieBreakOrders = election.races[race_index].candidates.map((Candidate) => (rng() as number))
results[race_index] = VotingMethods[voting_method](candidateNames, cvr, num_winners, tieBreakOrders)
results[race_index] = {
votingMethod: voting_method,
results: VotingMethods[voting_method](candidateNames, cvr, num_winners, tieBreakOrders)
}
}

res.json(
{
Election: election,
Results: results
election: election,
results: results
}
)
}
Expand Down
11 changes: 8 additions & 3 deletions backend/src/Controllers/sandboxController.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { ElectionResults } from '../../../domain_model/ITabulators';
import Logger from '../Services/Logging/Logger';
const className = "Elections.Controllers";
import { VotingMethods } from '../Tabulators/VotingMethodSelecter'
Expand All @@ -14,11 +15,15 @@ const getSandboxResults = async (req: Request, res: Response, next: NextFunction
if (!VotingMethods[voting_method]) {
throw new Error('Invalid Voting Method')
}
let results = VotingMethods[voting_method](candidateNames, cvr, num_winners)

let results: ElectionResults = {
votingMethod: voting_method,
results: VotingMethods[voting_method](candidateNames, cvr, num_winners)
}

res.json(
{
Results: results,
voting_method: voting_method
results: results
}
);
}
Expand Down
2 changes: 1 addition & 1 deletion backend/src/Tabulators/AllocatedScore.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ballot, candidate, fiveStarCount, allocatedScoreResults, allocatedScoreSummaryData, summaryData, totalScore } from "./ITabulators";
import { ballot, candidate, fiveStarCount, allocatedScoreResults, allocatedScoreSummaryData, totalScore } from "./../../../domain_model/ITabulators";

import { IparsedData } from './ParseData'
import Fraction from 'fraction.js'
Expand Down
2 changes: 1 addition & 1 deletion backend/src/Tabulators/Approval.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { approvalResults, approvalSummaryData, ballot, candidate, totalScore } from "./ITabulators";
import { approvalResults, approvalSummaryData, ballot, candidate, totalScore } from "./../../../domain_model/ITabulators";

import { IparsedData } from './ParseData'
const ParseData = require("./ParseData");
Expand Down
2 changes: 1 addition & 1 deletion backend/src/Tabulators/IRV.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ballot, candidate, irvResults, irvSummaryData, totalScore } from "./ITabulators";
import { ballot, candidate, irvResults, irvSummaryData, totalScore } from "./../../../domain_model/ITabulators";

import { IparsedData } from './ParseData'
const ParseData = require("./ParseData");
Expand Down
141 changes: 0 additions & 141 deletions backend/src/Tabulators/ITabulators.ts

This file was deleted.

2 changes: 1 addition & 1 deletion backend/src/Tabulators/ParseData.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ballot, voter } from "./ITabulators";
import { ballot, voter } from "./../../../domain_model/ITabulators";

// Functions to parse STAR scores
export interface IparsedData {
Expand Down
2 changes: 1 addition & 1 deletion backend/src/Tabulators/Plurality.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { approvalResults, approvalSummaryData, ballot, candidate, pluralityResults, pluralitySummaryData, totalScore } from "./ITabulators";
import { approvalResults, approvalSummaryData, ballot, candidate, pluralityResults, pluralitySummaryData, totalScore } from "./../../../domain_model/ITabulators";

import { IparsedData } from './ParseData'
const ParseData = require("./ParseData");
Expand Down
20 changes: 1 addition & 19 deletions backend/src/Tabulators/RankedRobin.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ballot, candidate, rankedRobinResults, rankedRobinSummaryData, results, roundResults, summaryData, totalScore } from "./ITabulators";
import { ballot, candidate, rankedRobinResults, rankedRobinSummaryData, roundResults, totalScore } from "./../../../domain_model/ITabulators";

import { IparsedData } from './ParseData'
import { sortByTieBreakOrder } from "./Star";
Expand Down Expand Up @@ -244,24 +244,6 @@ function getWinners(summaryData: rankedRobinSummaryData, eligibleCandidates: can
return winners
}

function runScoreTiebreaker(summaryData: summaryData, scoreWinners: candidate[]) {
// Search for weak condorcet winner between tied candidates
// Iterates through each candidate, if candidate does not lose to any other tied candidate, mark as winner
const condorcetWinners: candidate[] = [];
scoreWinners.forEach(a => {
let isWinner = true
scoreWinners.forEach(b => {
if (summaryData.pairwiseMatrix[b.index][a.index] === 1) {
isWinner = false
}
})
if (isWinner) condorcetWinners.push(a)
})
if (condorcetWinners.length === 1) return condorcetWinners
else if (condorcetWinners.length === 0) return scoreWinners
else return condorcetWinners
}

function sortMatrix(matrix: number[][], order: number[]) {
var newMatrix = Array(order.length);
for (let i = 0; i < order.length; i++) {
Expand Down
4 changes: 2 additions & 2 deletions backend/src/Tabulators/Star.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Star, runStarRound } from './Star'
import { summaryData } from './ITabulators'
import { starSummaryData } from './../../../domain_model/ITabulators'

describe("STAR Tests", () => {
test("Condorcet Winner", () => {
Expand Down Expand Up @@ -147,7 +147,7 @@ function buildTestSummaryData(candidates: string[], scores: number[], pairwiseMa
nInvalidVotes: 0,
nUnderVotes: 0,
nBulletVotes: 0
} as summaryData
} as starSummaryData
}

describe("STAR Score Round Tests", () => {
Expand Down
20 changes: 10 additions & 10 deletions backend/src/Tabulators/Star.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ballot, candidate, fiveStarCount, results, roundResults, summaryData, totalScore } from "./ITabulators";
import { ballot, candidate, fiveStarCount, starResults, roundResults, starSummaryData, totalScore } from "./../../../domain_model/ITabulators";

import { IparsedData } from './ParseData'
const ParseData = require("./ParseData");
Expand Down Expand Up @@ -31,7 +31,7 @@ export function Star(candidates: string[], votes: ballot[], nWinners = 1, random
const summaryData = getSummaryData(candidates, parsedData,randomTiebreakOrder)

// Initialize output data structure
const results: results = {
const results: starResults = {
elected: [],
tied: [],
other: [],
Expand Down Expand Up @@ -66,7 +66,7 @@ export function Star(candidates: string[], votes: ballot[], nWinners = 1, random
return results
}

function getSummaryData(candidates: string[], parsedData: IparsedData, randomTiebreakOrder: number[]): summaryData {
function getSummaryData(candidates: string[], parsedData: IparsedData, randomTiebreakOrder: number[]): starSummaryData {
const nCandidates = candidates.length
if (randomTiebreakOrder.length < nCandidates) {
randomTiebreakOrder = candidates.map((c,index) => index)
Expand Down Expand Up @@ -140,7 +140,7 @@ function getSummaryData(candidates: string[], parsedData: IparsedData, randomTie
}
}

function sortData(summaryData: summaryData, order: candidate[]): summaryData {
function sortData(summaryData: starSummaryData, order: candidate[]): starSummaryData {
// sorts summary data to be in specified order
const indexOrder = order.map(c => c.index)
const candidates = indexOrder.map(ind => (summaryData.candidates[ind]))
Expand All @@ -164,7 +164,7 @@ function sortData(summaryData: summaryData, order: candidate[]): summaryData {
}
}

export function runStarRound(summaryData: summaryData, remainingCandidates: candidate[], breakTiesRandomly = true, enablefiveStarTiebreaker = true) {
export function runStarRound(summaryData: starSummaryData, remainingCandidates: candidate[], breakTiesRandomly = true, enablefiveStarTiebreaker = true) {
// Initialize output results data structure
const roundResults: roundResults = {
winners: [],
Expand Down Expand Up @@ -331,7 +331,7 @@ export function runStarRound(summaryData: summaryData, remainingCandidates: cand
return roundResults
}

function getScoreWinners(summaryData: summaryData, eligibleCandidates: candidate[]) {
function getScoreWinners(summaryData: starSummaryData, eligibleCandidates: candidate[]) {
// Searches for candidate(s) with highest score

// Sort candidate total scores
Expand All @@ -355,7 +355,7 @@ function getScoreWinners(summaryData: summaryData, eligibleCandidates: candidate
}


function runRunoffTiebreaker(summaryData: summaryData, runoffCandidates: candidate[]) {
function runRunoffTiebreaker(summaryData: starSummaryData, runoffCandidates: candidate[]) {
// Search for candidate with highest score between two runoff candidates
if (summaryData.totalScores[runoffCandidates[0].index].score > summaryData.totalScores[runoffCandidates[1].index].score) {
return 0
Expand Down Expand Up @@ -386,7 +386,7 @@ function sortMatrix(matrix: number[][], order: number[]) {
return newMatrix
}

function fiveStarTiebreaker(summaryData: summaryData, candidates: candidate[], nCandidatesNeeded: number) {
function fiveStarTiebreaker(summaryData: starSummaryData, candidates: candidate[], nCandidatesNeeded: number) {
// Search for candidates with most five-star votes
let maxFiveStarVotes = 0
let fiveStarWinners: candidate[] = []
Expand All @@ -403,7 +403,7 @@ function fiveStarTiebreaker(summaryData: summaryData, candidates: candidate[], n
return fiveStarWinners
}

function getFiveStarCounts(summaryData: summaryData, tiedCandidates: candidate[]) {
function getFiveStarCounts(summaryData: starSummaryData, tiedCandidates: candidate[]) {
// Returns five star counts of tied candidates, sorted from most to least
const fiveStarCounts: fiveStarCount[] = []
tiedCandidates.forEach((candidate) => {
Expand All @@ -424,7 +424,7 @@ function getFiveStarLosers(fiveStarCounts: fiveStarCount[]) {
return fiveStarLosers.map(fiveStarLoser => fiveStarLoser.candidate)
}

function getHeadToHeadLosers(summaryData: summaryData, tiedCandidates: candidate[]) {
function getHeadToHeadLosers(summaryData: starSummaryData, tiedCandidates: candidate[]) {
// Search for candidates with most head to head losses
let headToHeadLosers: candidate[] = []
let maxLosses: number = 0
Expand Down
Loading

0 comments on commit cc64c92

Please sign in to comment.