Display order of election in count report

This commit is contained in:
RunasSudo 2021-01-05 03:37:09 +11:00
parent 7afd8d7bfd
commit 7ee56f97f8
Signed by: RunasSudo
GPG Key ID: 7234E476BF21C61A
5 changed files with 82 additions and 55 deletions

View File

@ -361,6 +361,32 @@ async function clickCount() {
elVRE.appendChild(elTd);
}
}
if (evt.data.type === 'done') {
let result = evt.data.result;
// Display results
for (let [candidate, countCard] of result.candidates) {
[elTr1, elTr2] = candMap[candidate];
elTd = document.createElement('td');
elTd.setAttribute('rowspan', '2');
if (countCard.state === py.pyRCV2.model.CandidateState.WITHDRAWN) {
elTd.classList.add('excluded');
elTd.innerHTML = 'Withdrawn';
} else if (countCard.state === py.pyRCV2.model.CandidateState.EXCLUDED || countCard.state === py.pyRCV2.model.CandidateState.EXCLUDING) {
elTd.classList.add('excluded');
elTd.innerHTML = 'Excluded ' + (-countCard.order_elected);
} else if (countCard.state === py.pyRCV2.model.CandidateState.ELECTED || countCard.state === py.pyRCV2.model.CandidateState.PROVISIONALLY_ELECTED || countCard.state === py.pyRCV2.model.CandidateState.DISTRIBUTING_SURPLUS) {
elTd.classList.add('elected');
elTd.innerHTML = 'ELECTED ' + countCard.order_elected;
}
elTd.style.borderTop = '1px solid black';
elTr1.appendChild(elTd);
}
elTd.style.borderBottom = '1px solid black';
}
}
worker.onerror = function(evt) {

View File

@ -65,26 +65,7 @@ onmessage = function(evt) {
stage = 1;
result = counter.reset();
postMessage({'type': 'result', 'result': {
'stage': stage,
'comment': result.comment,
'candidates': result.candidates.py_items().map(([c, cc]) => [c.py_name, {
'transfers': cc.transfers.pp(ppDP),
'votes': cc.votes.pp(ppDP),
'state': cc.state
}]),
'exhausted': {
'transfers': result.exhausted.transfers.pp(ppDP),
'votes': result.exhausted.votes.pp(ppDP)
},
'loss_fraction': {
'transfers': result.loss_fraction.transfers.pp(ppDP),
'votes': result.loss_fraction.votes.pp(ppDP)
},
'total': result.total.pp(ppDP),
'quota': result.quota.pp(ppDP),
'vote_required_election': result.vote_required_election === null ? null : result.vote_required_election.pp(ppDP),
}});
postMessage({'type': 'result', 'result': resultToJS(result)});
stepElection();
} else if (evt.data.type === 'require_input') {
@ -115,29 +96,34 @@ function stepElection() {
}
if (py.isinstance(result, py.pyRCV2.model.CountCompleted)) {
postMessage({'type': 'done'});
postMessage({'type': 'done', 'result': resultToJS(result)});
break;
} else {
postMessage({'type': 'result', 'result': {
'stage': stage,
'comment': result.comment,
'candidates': result.candidates.py_items().map(([c, cc]) => [c.py_name, {
'transfers': cc.transfers.pp(ppDP),
'votes': cc.votes.pp(ppDP),
'state': cc.state
}]),
'exhausted': {
'transfers': result.exhausted.transfers.pp(ppDP),
'votes': result.exhausted.votes.pp(ppDP)
},
'loss_fraction': {
'transfers': result.loss_fraction.transfers.pp(ppDP),
'votes': result.loss_fraction.votes.pp(ppDP)
},
'total': result.total.pp(ppDP),
'quota': result.quota.pp(ppDP),
'vote_required_election': result.vote_required_election === null ? null : result.vote_required_election.pp(ppDP),
}});
postMessage({'type': 'result', 'result': resultToJS(result)});
}
}
}
function resultToJS(result) {
return {
'stage': stage,
'comment': result.comment,
'candidates': result.candidates.py_items().map(([c, cc]) => [c.py_name, {
'transfers': cc.transfers.pp(ppDP),
'votes': cc.votes.pp(ppDP),
'state': cc.state,
'order_elected': cc.order_elected,
}]),
'exhausted': {
'transfers': result.exhausted.transfers.pp(ppDP),
'votes': result.exhausted.votes.pp(ppDP)
},
'loss_fraction': {
'transfers': result.loss_fraction.transfers.pp(ppDP),
'votes': result.loss_fraction.votes.pp(ppDP)
},
'total': result.total.pp(ppDP),
'quota': result.quota.pp(ppDP),
'vote_required_election': result.vote_required_election === null ? null : result.vote_required_election.pp(ppDP),
};
}

View File

@ -60,9 +60,9 @@ def print_step(args, stage, result):
for candidate, count_card in results:
state = None
if count_card.state == pyRCV2.model.CandidateState.ELECTED or count_card.state == pyRCV2.model.CandidateState.PROVISIONALLY_ELECTED or count_card.state == pyRCV2.model.CandidateState.DISTRIBUTING_SURPLUS:
state = 'ELECTED'
state = 'ELECTED {}'.format(count_card.order_elected)
elif count_card.state == pyRCV2.model.CandidateState.EXCLUDED or count_card.state == pyRCV2.model.CandidateState.EXCLUDING:
state = 'Excluded'
state = 'Excluded {}'.format(-count_card.order_elected)
elif count_card.state == pyRCV2.model.CandidateState.WITHDRAWN:
state = 'Withdrawn'

View File

@ -89,6 +89,7 @@ class BaseSTVCounter:
self.total_orig = sum((b.value for b in self.election.ballots), Num('0'))
self.num_elected = 0
self.num_excluded = 0
# Withdraw candidates
for candidate in self.election.withdrawn:
@ -196,17 +197,28 @@ class BaseSTVCounter:
# Have sufficient candidates been elected?
if self.num_elected >= self.election.seats:
return CountCompleted()
__pragma__('opov')
return CountCompleted(
'Count complete',
self.candidates,
self.exhausted,
self.loss_fraction,
self.total + self.exhausted.votes + self.loss_fraction.votes,
self.quota,
self.vote_required_election,
)
__pragma__('noopov')
# Are there just enough candidates to fill all the seats?
if self.options['bulk_elect']:
if self.num_elected + sum(1 for c, cc in self.candidates.items() if cc.state == CandidateState.HOPEFUL or cc.state == CandidateState.EXCLUDING) <= self.election.seats:
# Include EXCLUDING to avoid interrupting an exclusion
if len(self.election.candidates) - self.num_excluded + sum(1 for c, cc in self.candidates.items() if cc.state == CandidateState.EXCLUDING) <= self.election.seats:
# Declare elected all remaining candidates
for candidate, count_card in self.candidates.items():
if count_card.state == CandidateState.HOPEFUL:
count_card.state = CandidateState.PROVISIONALLY_ELECTED
count_card.order_elected = self.num_elected
self.num_elected += 1
count_card.order_elected = self.num_elected
__pragma__('opov')
result = CountStepResult(
@ -338,15 +350,15 @@ class BaseSTVCounter:
# If we did not perform bulk election in before_surpluses: Are there just enough candidates to fill all the seats?
if not self.options['bulk_elect']:
if self.num_elected + sum(1 for c, cc in self.candidates.items() if cc.state == CandidateState.HOPEFUL) <= self.election.seats:
if len(self.election.candidates) - self.num_excluded <= self.election.seats:
# Declare elected one remaining candidate at a time
hopefuls = [(c, cc) for c, cc in self.candidates.items() if cc.state == CandidateState.HOPEFUL]
hopefuls.sort(key=lambda x: x[1].votes, reverse=True)
candidate_elected, count_card = self.choose_highest(hopefuls)
count_card.state = CandidateState.PROVISIONALLY_ELECTED
count_card.order_elected = self.num_elected
self.num_elected += 1
count_card.order_elected = self.num_elected
__pragma__('opov')
result = CountStepResult(
@ -370,7 +382,10 @@ class BaseSTVCounter:
candidates_excluded = self.candidates_to_exclude()
for candidate, count_card in candidates_excluded:
count_card.state = CandidateState.EXCLUDING
if count_card.state != CandidateState.EXCLUDING:
count_card.state = CandidateState.EXCLUDING
self.num_excluded += 1
count_card.order_elected = -self.num_excluded
# Handle Wright STV
if self.options['exclusion'] == 'wright':
@ -433,7 +448,7 @@ class BaseSTVCounter:
Returns List[Tuple[Candidate, CountCard]]
"""
remaining_candidates = self.num_elected + sum(1 for c, cc in self.candidates.items() if cc.state == CandidateState.HOPEFUL)
remaining_candidates = len(self.election.candidates) - self.num_excluded
__pragma__('opov')
total_surpluses = sum((cc.votes - self.quota for c, cc in self.candidates.items() if cc.votes > self.quota), Num(0))
__pragma__('noopov')
@ -574,8 +589,8 @@ class BaseSTVCounter:
candidate, count_card = x[0], x[1]
count_card.state = CandidateState.PROVISIONALLY_ELECTED
count_card.order_elected = self.num_elected
self.num_elected += 1
count_card.order_elected = self.num_elected
meets_quota.remove(x)

View File

@ -65,7 +65,7 @@ class CountCard:
self.orig_votes = Num('0')
self.transfers = Num('0')
self.state = CandidateState.HOPEFUL
self.order_elected = None
self.order_elected = None # Negative for order of exclusion
# self.parcels = List[Parcel]
# Parcel = List[Tuple[Ballot, Num]]
@ -94,9 +94,6 @@ class CountCard:
result.order_elected = self.order_elected
return result
class CountCompleted:
pass
class CountStepResult:
def __init__(self, comment, candidates, exhausted, loss_fraction, total, quota, vote_required_election):
self.comment = comment
@ -119,3 +116,6 @@ class CountStepResult:
__pragma__('noopov')
return CountStepResult(self.comment, candidates, self.exhausted.clone(), self.loss_fraction.clone(), self.total, self.quota)
class CountCompleted(CountStepResult):
pass