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); 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) { worker.onerror = function(evt) {

View File

@ -65,26 +65,7 @@ onmessage = function(evt) {
stage = 1; stage = 1;
result = counter.reset(); result = counter.reset();
postMessage({'type': 'result', 'result': { postMessage({'type': 'result', 'result': resultToJS(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),
}});
stepElection(); stepElection();
} else if (evt.data.type === 'require_input') { } else if (evt.data.type === 'require_input') {
@ -115,29 +96,34 @@ function stepElection() {
} }
if (py.isinstance(result, py.pyRCV2.model.CountCompleted)) { if (py.isinstance(result, py.pyRCV2.model.CountCompleted)) {
postMessage({'type': 'done'}); postMessage({'type': 'done', 'result': resultToJS(result)});
break; break;
} else { } else {
postMessage({'type': 'result', 'result': { postMessage({'type': 'result', 'result': resultToJS(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),
}});
} }
} }
} }
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: for candidate, count_card in results:
state = None 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: 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: 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: elif count_card.state == pyRCV2.model.CandidateState.WITHDRAWN:
state = '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.total_orig = sum((b.value for b in self.election.ballots), Num('0'))
self.num_elected = 0 self.num_elected = 0
self.num_excluded = 0
# Withdraw candidates # Withdraw candidates
for candidate in self.election.withdrawn: for candidate in self.election.withdrawn:
@ -196,17 +197,28 @@ class BaseSTVCounter:
# Have sufficient candidates been elected? # Have sufficient candidates been elected?
if self.num_elected >= self.election.seats: 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? # Are there just enough candidates to fill all the seats?
if self.options['bulk_elect']: 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 # Declare elected all remaining candidates
for candidate, count_card in self.candidates.items(): for candidate, count_card in self.candidates.items():
if count_card.state == CandidateState.HOPEFUL: if count_card.state == CandidateState.HOPEFUL:
count_card.state = CandidateState.PROVISIONALLY_ELECTED count_card.state = CandidateState.PROVISIONALLY_ELECTED
count_card.order_elected = self.num_elected
self.num_elected += 1 self.num_elected += 1
count_card.order_elected = self.num_elected
__pragma__('opov') __pragma__('opov')
result = CountStepResult( 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 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 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 # Declare elected one remaining candidate at a time
hopefuls = [(c, cc) for c, cc in self.candidates.items() if cc.state == CandidateState.HOPEFUL] 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) hopefuls.sort(key=lambda x: x[1].votes, reverse=True)
candidate_elected, count_card = self.choose_highest(hopefuls) candidate_elected, count_card = self.choose_highest(hopefuls)
count_card.state = CandidateState.PROVISIONALLY_ELECTED count_card.state = CandidateState.PROVISIONALLY_ELECTED
count_card.order_elected = self.num_elected
self.num_elected += 1 self.num_elected += 1
count_card.order_elected = self.num_elected
__pragma__('opov') __pragma__('opov')
result = CountStepResult( result = CountStepResult(
@ -370,7 +382,10 @@ class BaseSTVCounter:
candidates_excluded = self.candidates_to_exclude() candidates_excluded = self.candidates_to_exclude()
for candidate, count_card in candidates_excluded: 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 # Handle Wright STV
if self.options['exclusion'] == 'wright': if self.options['exclusion'] == 'wright':
@ -433,7 +448,7 @@ class BaseSTVCounter:
Returns List[Tuple[Candidate, CountCard]] 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') __pragma__('opov')
total_surpluses = sum((cc.votes - self.quota for c, cc in self.candidates.items() if cc.votes > self.quota), Num(0)) total_surpluses = sum((cc.votes - self.quota for c, cc in self.candidates.items() if cc.votes > self.quota), Num(0))
__pragma__('noopov') __pragma__('noopov')
@ -574,8 +589,8 @@ class BaseSTVCounter:
candidate, count_card = x[0], x[1] candidate, count_card = x[0], x[1]
count_card.state = CandidateState.PROVISIONALLY_ELECTED count_card.state = CandidateState.PROVISIONALLY_ELECTED
count_card.order_elected = self.num_elected
self.num_elected += 1 self.num_elected += 1
count_card.order_elected = self.num_elected
meets_quota.remove(x) meets_quota.remove(x)

View File

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