Visual improvements to HTML/JS interface

This commit is contained in:
RunasSudo 2021-05-25 01:12:26 +10:00
parent 5c185b386c
commit 1ad1684e67
Signed by: RunasSudo
GPG Key ID: 7234E476BF21C61A
10 changed files with 151 additions and 73 deletions

View File

@ -202,7 +202,8 @@ async function clickCount() {
// Step election // Step election
let worker = new Worker('worker.js'); let worker = new Worker('worker.js');
let election; let election;
let trComment, trExhausted1, trExhausted2, trLTF1, trLTF2, trTotal, trQuota, trVRE; let trStageNo, trStageKind, trComment;
let trExhausted1, trExhausted2, trLTF1, trLTF2, trTotal, trQuota, trVRE;
let olLogs; let olLogs;
worker.onmessage = function(evt) { worker.onmessage = function(evt) {
@ -220,9 +221,22 @@ async function clickCount() {
if (evt.data.type === 'init') { if (evt.data.type === 'init') {
election = evt.data.election; election = evt.data.election;
// Comment row // Comment rows
trComment = document.createElement('tr'); trStageNo = document.createElement('tr');
trStageNo.classList.add('stage-no');
let elTd = document.createElement('td'); let elTd = document.createElement('td');
trStageNo.appendChild(elTd);
tblResults.appendChild(trStageNo);
trStageKind = document.createElement('tr');
trStageKind.classList.add('stage-kind');
elTd = document.createElement('td');
trStageKind.appendChild(elTd);
tblResults.appendChild(trStageKind);
trComment = document.createElement('tr');
trComment.classList.add('stage-comment');
elTd = document.createElement('td');
trComment.appendChild(elTd); trComment.appendChild(elTd);
tblResults.appendChild(trComment); tblResults.appendChild(trComment);
@ -233,7 +247,7 @@ async function clickCount() {
elTd = document.createElement('td'); elTd = document.createElement('td');
elTd.setAttribute('rowspan', '2'); elTd.setAttribute('rowspan', '2');
elTd.style.borderTop = '1px solid black'; elTd.classList.add('bt');
elTd.innerText = candidate; elTd.innerText = candidate;
elTr1.appendChild(elTd); elTr1.appendChild(elTd);
@ -251,7 +265,7 @@ async function clickCount() {
elTd = document.createElement('td'); elTd = document.createElement('td');
elTd.setAttribute('rowspan', '2'); elTd.setAttribute('rowspan', '2');
elTd.style.borderTop = '1px solid black'; elTd.classList.add('bt');
elTd.innerText = 'Exhausted'; elTd.innerText = 'Exhausted';
trExhausted1.appendChild(elTd); trExhausted1.appendChild(elTd);
@ -268,7 +282,7 @@ async function clickCount() {
elTd = document.createElement('td'); elTd = document.createElement('td');
elTd.setAttribute('rowspan', '2'); elTd.setAttribute('rowspan', '2');
elTd.style.borderTop = '1px solid black'; elTd.classList.add('bt');
elTd.innerText = 'Loss to fraction'; elTd.innerText = 'Loss to fraction';
trLTF1.appendChild(elTd); trLTF1.appendChild(elTd);
@ -279,7 +293,7 @@ async function clickCount() {
trTotal = document.createElement('tr'); trTotal = document.createElement('tr');
trTotal.classList.add('info'); trTotal.classList.add('info');
elTd = document.createElement('td'); elTd = document.createElement('td');
elTd.style.borderTop = '1px solid black'; elTd.classList.add('bt');
elTd.innerText = 'Total'; elTd.innerText = 'Total';
trTotal.appendChild(elTd); trTotal.appendChild(elTd);
tblResults.appendChild(trTotal); tblResults.appendChild(trTotal);
@ -288,8 +302,8 @@ async function clickCount() {
trQuota = document.createElement('tr'); trQuota = document.createElement('tr');
trQuota.classList.add('info'); trQuota.classList.add('info');
elTd = document.createElement('td'); elTd = document.createElement('td');
elTd.style.borderTop = '1px solid black'; elTd.classList.add('bt');
elTd.style.borderBottom = '1px solid black'; elTd.classList.add('bb');
elTd.innerText = 'Quota'; elTd.innerText = 'Quota';
trQuota.appendChild(elTd); trQuota.appendChild(elTd);
tblResults.appendChild(trQuota); tblResults.appendChild(trQuota);
@ -299,8 +313,8 @@ async function clickCount() {
trVRE = document.createElement('tr'); trVRE = document.createElement('tr');
trVRE.classList.add('info'); trVRE.classList.add('info');
elTd = document.createElement('td'); elTd = document.createElement('td');
elTd.style.borderTop = '1px solid black'; elTd.classList.add('bt');
elTd.style.borderBottom = '1px solid black'; elTd.classList.add('bb');
elTd.innerText = 'Vote required for election'; elTd.innerText = 'Vote required for election';
trVRE.appendChild(elTd); trVRE.appendChild(elTd);
tblResults.appendChild(trVRE); tblResults.appendChild(trVRE);
@ -336,7 +350,15 @@ async function clickCount() {
// Display results // Display results
elTd = document.createElement('td'); elTd = document.createElement('td');
elTd.innerText = result.stage + '. ' + result.comment; elTd.innerText = result.stage;
trStageNo.appendChild(elTd);
elTd = document.createElement('td');
elTd.innerText = result.stage_kind;
trStageKind.appendChild(elTd);
elTd = document.createElement('td');
elTd.innerText = result.comment;
trComment.appendChild(elTd); trComment.appendChild(elTd);
for (let [candidate, countCard] of result.candidates) { for (let [candidate, countCard] of result.candidates) {
@ -349,7 +371,7 @@ async function clickCount() {
} 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) { } 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.classList.add('elected');
} }
elTd.style.borderTop = '1px solid black'; elTd.classList.add('bt');
elTd.innerHTML = ppVotes(countCard.transfers); elTd.innerHTML = ppVotes(countCard.transfers);
elTr1.appendChild(elTd); elTr1.appendChild(elTd);
@ -383,7 +405,7 @@ async function clickCount() {
// Display exhausted votes // Display exhausted votes
elTd = document.createElement('td'); elTd = document.createElement('td');
elTd.classList.add('count'); elTd.classList.add('count');
elTd.style.borderTop = '1px solid black'; elTd.classList.add('bt');
elTd.innerHTML = ppVotes(result.exhausted.transfers); elTd.innerHTML = ppVotes(result.exhausted.transfers);
trExhausted1.appendChild(elTd); trExhausted1.appendChild(elTd);
@ -395,7 +417,7 @@ async function clickCount() {
// Display loss to fraction // Display loss to fraction
elTd = document.createElement('td'); elTd = document.createElement('td');
elTd.classList.add('count'); elTd.classList.add('count');
elTd.style.borderTop = '1px solid black'; elTd.classList.add('bt');
elTd.innerHTML = ppVotes(result.loss_fraction.transfers); elTd.innerHTML = ppVotes(result.loss_fraction.transfers);
trLTF1.appendChild(elTd); trLTF1.appendChild(elTd);
@ -413,15 +435,15 @@ async function clickCount() {
// Display total // Display total
elTd = document.createElement('td'); elTd = document.createElement('td');
elTd.classList.add('count'); elTd.classList.add('count');
elTd.style.borderTop = '1px solid black'; elTd.classList.add('bt');
elTd.innerHTML = ppVotes(result.total); elTd.innerHTML = ppVotes(result.total);
trTotal.appendChild(elTd); trTotal.appendChild(elTd);
// Display quota // Display quota
elTd = document.createElement('td'); elTd = document.createElement('td');
elTd.classList.add('count'); elTd.classList.add('count');
elTd.style.borderTop = '1px solid black'; elTd.classList.add('bt');
elTd.style.borderBottom = '1px solid black'; elTd.classList.add('bb');
elTd.innerHTML = ppVotes(result.quota); elTd.innerHTML = ppVotes(result.quota);
trQuota.appendChild(elTd); trQuota.appendChild(elTd);
@ -429,8 +451,8 @@ async function clickCount() {
if (result.vote_required_election !== null) { if (result.vote_required_election !== null) {
elTd = document.createElement('td'); elTd = document.createElement('td');
elTd.classList.add('count'); elTd.classList.add('count');
elTd.style.borderTop = '1px solid black'; elTd.classList.add('bt');
elTd.style.borderBottom = '1px solid black'; elTd.classList.add('bb');
elTd.innerHTML = ppVotes(result.vote_required_election); elTd.innerHTML = ppVotes(result.vote_required_election);
trVRE.appendChild(elTd); trVRE.appendChild(elTd);
} }
@ -462,11 +484,11 @@ async function clickCount() {
elTd.innerHTML = 'ELECTED ' + countCard.order_elected; elTd.innerHTML = 'ELECTED ' + countCard.order_elected;
winners.append([candidate, countCard]); winners.append([candidate, countCard]);
} }
elTd.style.borderTop = '1px solid black'; elTd.classList.add('bt');
elTr1.appendChild(elTd); elTr1.appendChild(elTd);
} }
elTd.style.borderBottom = '1px solid black'; elTd.classList.add('bb');
let elP = document.createElement('p'); let elP = document.createElement('p');
elP.innerText = 'Count complete. The winning candidates are, in order of election:' elP.innerText = 'Count complete. The winning candidates are, in order of election:'
@ -541,6 +563,7 @@ async function clickCount() {
if (result.indexOf('.') >= 0) { if (result.indexOf('.') >= 0) {
result = result.substring(0, result.indexOf('.')) + '<sup>' + result.substring(result.indexOf('.')) + '</sup>'; result = result.substring(0, result.indexOf('.')) + '<sup>' + result.substring(result.indexOf('.')) + '</sup>';
} }
result = result.replace('-', '−');
return result; return result;
} }
} }

View File

@ -16,14 +16,25 @@
along with this program. If not, see <https://www.gnu.org/licenses/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
@import url('https://fonts.googleapis.com/css2?family=Source+Sans+Pro&display=swap');
html, body { html, body {
font-family: 'Liberation Sans', FreeSans, Helvetica, Arial, sans-serif; font-family: 'Source Sans Pro', sans-serif;
} }
body { body {
padding: 0.5em; padding: 0.5em;
} }
a {
color: #1d46c4;
text-decoration: none;
}
a:hover {
color: #1d3da2;
text-decoration: underline;
}
/* Menu styling */ /* Menu styling */
.menudiv { .menudiv {
@ -69,24 +80,41 @@ td.count sup {
font-size: 0.6rem; font-size: 0.6rem;
top: 0; top: 0;
} }
.result tr:first-child td { tr.stage-no td, tr.stage-kind td, tr.stage-comment td {
vertical-align: bottom; text-align: center;
}
tr.stage-no td:not(:first-child) {
border-top: 1px solid #76858c;
}
tr.stage-kind td:not(:first-child) {
font-size: 0.75em;
min-width: 5rem;
color: #1b2839;
background-color: #f0f5fb;
color-adjust: exact;
-webkit-print-color-adjust: exact;
} }
td.excluded { td.excluded {
background-color: #fecfcfff; background-color: #fde2e2;
color-adjust: exact; color-adjust: exact;
-webkit-print-color-adjust: exact; -webkit-print-color-adjust: exact;
} }
td.elected { td.elected {
background-color: #d1fca7ff; background-color: #e0fdc5;
color-adjust: exact; color-adjust: exact;
-webkit-print-color-adjust: exact; -webkit-print-color-adjust: exact;
} }
tr.info td { tr.info td {
background-color: #edededff; background-color: #f0f5fb;
color-adjust: exact; color-adjust: exact;
-webkit-print-color-adjust: exact; -webkit-print-color-adjust: exact;
} }
td.bt {
border-top: 1px solid #76858c;
}
td.bb {
border-bottom: 1px solid #76858c;
}
/* BLT input tool */ /* BLT input tool */

View File

@ -145,6 +145,7 @@ function handleException(ex) {
function resultToJS(result) { function resultToJS(result) {
return { return {
'stage': stage, 'stage': stage,
'stage_kind': result.stage_kind,
'comment': result.comment, 'comment': result.comment,
'logs': result.logs, 'logs': result.logs,
'candidates': result.candidates.py_items().map(([c, cc]) => [c.py_name, { 'candidates': result.candidates.py_items().map(([c, cc]) => [c.py_name, {

View File

@ -61,6 +61,9 @@ def add_parser(subparsers):
parser.add_argument('--pp-decimals', type=int, default=2, help='print votes to specified decimal places in results report (default: 2)') parser.add_argument('--pp-decimals', type=int, default=2, help='print votes to specified decimal places in results report (default: 2)')
def print_step(args, stage, result): def print_step(args, stage, result):
if result.stage_kind:
print('{}. {} {}'.format(stage, result.stage_kind, result.comment))
else:
print('{}. {}'.format(stage, result.comment)) print('{}. {}'.format(stage, result.comment))
if result.logs: if result.logs:

View File

@ -99,7 +99,7 @@ class BaseSTVCounter:
self.compute_quota() self.compute_quota()
self.elect_meeting_quota() self.elect_meeting_quota()
return self.make_result('First preferences') return self.make_result(None, 'First preferences')
def distribute_first_preferences(self): def distribute_first_preferences(self):
""" """
@ -180,6 +180,7 @@ class BaseSTVCounter:
if self.num_elected >= self.election.seats: if self.num_elected >= self.election.seats:
__pragma__('opov') __pragma__('opov')
return CountCompleted( return CountCompleted(
None,
'Count complete', 'Count complete',
self.logs, self.logs,
self.candidates, self.candidates,
@ -193,7 +194,6 @@ class BaseSTVCounter:
# 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']:
# Include EXCLUDING to avoid interrupting an exclusion
if len(self.election.candidates) - self.num_withdrawn - self.num_excluded + sum(1 for c, cc in self.candidates.items() if cc.state == CandidateState.EXCLUDING) <= self.election.seats: if len(self.election.candidates) - self.num_withdrawn - 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
candidates_elected = [(c, cc) for c, cc in self.candidates.items() if cc.state == CandidateState.HOPEFUL or cc.state == CandidateState.GUARDED] candidates_elected = [(c, cc) for c, cc in self.candidates.items() if cc.state == CandidateState.HOPEFUL or cc.state == CandidateState.GUARDED]
@ -212,7 +212,7 @@ class BaseSTVCounter:
constraints.stabilise_matrix(self) constraints.stabilise_matrix(self)
self.logs.extend(constraints.guard_or_doom(self)) self.logs.extend(constraints.guard_or_doom(self))
return self.make_result('Bulk election') return self.make_result(None, 'Bulk election')
def can_defer_surpluses(self, has_surplus): def can_defer_surpluses(self, has_surplus):
""" """
@ -301,7 +301,7 @@ class BaseSTVCounter:
self.elect_meeting_quota() self.elect_meeting_quota()
return self.make_result('Surplus of ' + candidate_surplus.name) return self.make_result('Surplus of', candidate_surplus.name)
def do_surplus(self, candidate_surplus, count_card, surplus): def do_surplus(self, candidate_surplus, count_card, surplus):
""" """
@ -317,7 +317,8 @@ 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 len(self.election.candidates) - self.num_withdrawn - self.num_excluded <= self.election.seats: # Include EXCLUDING to avoid interrupting an exclusion
if len(self.election.candidates) - self.num_withdrawn - self.num_excluded + sum(1 for c, cc in self.candidates.items() if cc.state == CandidateState.EXCLUDING) <= 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 or cc.state == CandidateState.GUARDED] hopefuls = [(c, cc) for c, cc in self.candidates.items() if cc.state == CandidateState.HOPEFUL or cc.state == CandidateState.GUARDED]
hopefuls.sort(key=lambda x: x[1].votes, reverse=True) hopefuls.sort(key=lambda x: x[1].votes, reverse=True)
@ -344,7 +345,7 @@ class BaseSTVCounter:
else: else:
self.logs.append(self.pretty_join(order_elected) + ' are elected to fill the remaining vacancies.') self.logs.append(self.pretty_join(order_elected) + ' are elected to fill the remaining vacancies.')
return self.make_result('Bulk election') return self.make_result(None, 'Bulk election')
def exclude_doomed(self): def exclude_doomed(self):
""" """
@ -369,6 +370,13 @@ class BaseSTVCounter:
Exclude the lowest ranked hopeful(s) Exclude the lowest ranked hopeful(s)
""" """
# Continue current exclusion if applicable
if self._exclusion is not None:
self.logs.append('Continuing exclusion of ' + self.pretty_join([c.name for c, cc in self._exclusion[0]]) + '.')
__pragma__('opov')
candidates_excluded = self._exclusion[0]
__pragma__('noopov')
else:
candidates_excluded = self.candidates_to_exclude() candidates_excluded = self.candidates_to_exclude()
if len(candidates_excluded) > 0: if len(candidates_excluded) > 0:
if len(candidates_excluded) == 1: if len(candidates_excluded) == 1:
@ -376,6 +384,7 @@ class BaseSTVCounter:
else: else:
self.logs.append('No surpluses to distribute, so ' + self.pretty_join([c.name for c, cc in candidates_excluded]) + ' are excluded.') self.logs.append('No surpluses to distribute, so ' + self.pretty_join([c.name for c, cc in candidates_excluded]) + ' are excluded.')
if len(candidates_excluded) > 0:
return self.exclude_candidates(candidates_excluded) return self.exclude_candidates(candidates_excluded)
def exclude_candidates(self, candidates_excluded): def exclude_candidates(self, candidates_excluded):
@ -434,7 +443,7 @@ class BaseSTVCounter:
self.elect_meeting_quota() self.elect_meeting_quota()
return self.make_result('Exclusion of ' + ', '.join([c.name for c, cc in candidates_excluded])) return self.make_result('Exclusion of', ', '.join([c.name for c, cc in candidates_excluded]))
def candidates_to_bulk_exclude(self, hopefuls): def candidates_to_bulk_exclude(self, hopefuls):
""" """
@ -479,13 +488,6 @@ class BaseSTVCounter:
Returns List[Tuple[Candidate, CountCard]] Returns List[Tuple[Candidate, CountCard]]
""" """
# Continue current exclusion if applicable
if self._exclusion is not None:
self.logs.append('Continuing exclusion of ' + self.pretty_join([c.name for c, cc in self._exclusion[0]]) + '.')
__pragma__('opov')
return self._exclusion[0]
__pragma__('noopov')
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) hopefuls.sort(key=lambda x: x[1].votes)
@ -775,9 +777,10 @@ class BaseSTVCounter:
return num return num
return num.round(self.options['round_tvs'], num.ROUND_DOWN) return num.round(self.options['round_tvs'], num.ROUND_DOWN)
def make_result(self, comment): def make_result(self, stage_kind, comment):
__pragma__('opov') __pragma__('opov')
result = CountStepResult( result = CountStepResult(
stage_kind,
comment, comment,
self.logs, self.logs,
self.candidates, self.candidates,

View File

@ -141,7 +141,7 @@ class MeekSTVCounter(BaseSTVCounter):
self.logs.append(self.total.pp(2) + ' usable votes, so the quota is ' + self.quota.pp(2) + '.') self.logs.append(self.total.pp(2) + ' usable votes, so the quota is ' + self.quota.pp(2) + '.')
self.elect_meeting_quota() self.elect_meeting_quota()
return self.make_result('First preferences') return self.make_result(None, 'First preferences')
def distribute_recursively(self, tree, remaining_multiplier): def distribute_recursively(self, tree, remaining_multiplier):
if tree.next_exhausted is None: if tree.next_exhausted is None:
@ -266,7 +266,7 @@ class MeekSTVCounter(BaseSTVCounter):
# NB: We could do this earlier, but this shows the flow of the election more clearly in the count sheet # NB: We could do this earlier, but this shows the flow of the election more clearly in the count sheet
self.elect_meeting_quota() self.elect_meeting_quota()
return self.make_result('Surpluses distributed') return self.make_result(None, 'Surpluses distributed')
def do_exclusion(self, candidates_excluded): def do_exclusion(self, candidates_excluded):
""" """

View File

@ -155,7 +155,8 @@ class CountCard:
__pragma__('noopov') __pragma__('noopov')
class CountStepResult: class CountStepResult:
def __init__(self, comment, logs, candidates, exhausted, loss_fraction, total, quota, vote_required_election): def __init__(self, stage_kind, comment, logs, candidates, exhausted, loss_fraction, total, quota, vote_required_election):
self.stage_kind = stage_kind
self.comment = comment self.comment = comment
self.logs = logs self.logs = logs
@ -176,7 +177,7 @@ class CountStepResult:
candidates[c] = cc.clone() candidates[c] = cc.clone()
__pragma__('noopov') __pragma__('noopov')
return CountStepResult(self.comment, candidates, self.exhausted.clone(), self.loss_fraction.clone(), self.total, self.quota) return CountStepResult(self.stage_kind, self.comment, candidates, self.exhausted.clone(), self.loss_fraction.clone(), self.total, self.quota)
class CountCompleted(CountStepResult): class CountCompleted(CountStepResult):
pass pass

View File

@ -60,7 +60,8 @@ def test_aec_tas19():
result = counter.step() result = counter.step()
comment = data[1][i] comment = data[1][i]
assert result.comment == comment, 'Failed to verify comment' result_comment = (result.stage_kind + ' ' + result.comment) if result.stage_kind else result.comment
assert result_comment == comment, 'Failed to verify comment'
for j, cand in enumerate(candidates): for j, cand in enumerate(candidates):
votes = pyRCV2.numbers.Num(data[j + 2][i]) votes = pyRCV2.numbers.Num(data[j + 2][i])

View File

@ -67,7 +67,8 @@ def test_ers97_py():
result = counter.step() result = counter.step()
comment = data[1][i] comment = data[1][i]
assert result.comment == comment, 'Failed to verify comment' result_comment = (result.stage_kind + ' ' + result.comment) if result.stage_kind else result.comment
assert result_comment == comment, 'Failed to verify comment'
for j, cand in enumerate(candidates): for j, cand in enumerate(candidates):
votes = pyRCV2.numbers.Num(data[j + 2][i]) votes = pyRCV2.numbers.Num(data[j + 2][i])
@ -112,7 +113,8 @@ def test_ers97_js():
assert ctx.eval('result = counter.step();') assert ctx.eval('result = counter.step();')
comment = data[1][i] comment = data[1][i]
assert ctx.eval('result.comment') == comment, 'Failed to verify comment' result_comment = ctx.eval('result.stage_kind ? (result.stage_kind + " " + result.comment) : result.comment')
assert result_comment == comment, 'Failed to verify comment'
for j, cand in enumerate(candidates): for j, cand in enumerate(candidates):
ctx.eval('votes = py.pyRCV2.numbers.Num("{}");'.format(data[j + 2][i])) ctx.eval('votes = py.pyRCV2.numbers.Num("{}");'.format(data[j + 2][i]))

View File

@ -69,7 +69,8 @@ def test_prsa1():
# Stage 2 # Stage 2
result = counter.step() result = counter.step()
assert result.comment == 'Surplus of Grey' assert result.stage_kind == 'Surplus of'
assert result.comment == 'Grey'
assert isclose(result.candidates[c_evans].votes, 2234) assert isclose(result.candidates[c_evans].votes, 2234)
assert isclose(result.candidates[c_grey].votes, 13001) assert isclose(result.candidates[c_grey].votes, 13001)
assert isclose(result.candidates[c_thomson].votes, 7468) assert isclose(result.candidates[c_thomson].votes, 7468)
@ -81,7 +82,8 @@ def test_prsa1():
# Stage 3 # Stage 3
result = counter.step() result = counter.step()
assert result.comment == 'Surplus of Ames' assert result.stage_kind == 'Surplus of'
assert result.comment == 'Ames'
assert isclose(result.candidates[c_evans].votes, 3038) assert isclose(result.candidates[c_evans].votes, 3038)
assert isclose(result.candidates[c_grey].votes, 13001) assert isclose(result.candidates[c_grey].votes, 13001)
assert isclose(result.candidates[c_thomson].votes, 8674) assert isclose(result.candidates[c_thomson].votes, 8674)
@ -93,7 +95,8 @@ def test_prsa1():
# Stage 4 # Stage 4
result = counter.step() result = counter.step()
assert result.comment == 'Surplus of Spears' assert result.stage_kind == 'Surplus of'
assert result.comment == 'Spears'
assert isclose(result.candidates[c_evans].votes, 4823) assert isclose(result.candidates[c_evans].votes, 4823)
assert isclose(result.candidates[c_grey].votes, 13001) assert isclose(result.candidates[c_grey].votes, 13001)
assert isclose(result.candidates[c_thomson].votes, 8674) assert isclose(result.candidates[c_thomson].votes, 8674)
@ -105,25 +108,29 @@ def test_prsa1():
# Stage 5 # Stage 5
result = counter.step() result = counter.step()
assert result.comment == 'Exclusion of Reid' assert result.stage_kind == 'Exclusion of'
assert result.comment == 'Reid'
assert isclose(result.candidates[c_reid].transfers, -1000) assert isclose(result.candidates[c_reid].transfers, -1000)
assert isclose(result.candidates[c_white].transfers, 1000) assert isclose(result.candidates[c_white].transfers, 1000)
# Stage 6 # Stage 6
result = counter.step() result = counter.step()
assert result.comment == 'Exclusion of Reid' assert result.stage_kind == 'Exclusion of'
assert result.comment == 'Reid'
assert isclose(result.candidates[c_reid].transfers, -617) assert isclose(result.candidates[c_reid].transfers, -617)
assert isclose(result.candidates[c_white].transfers, 617) assert isclose(result.candidates[c_white].transfers, 617)
# Stage 7 # Stage 7
result = counter.step() result = counter.step()
assert result.comment == 'Exclusion of Reid' assert result.stage_kind == 'Exclusion of'
assert result.comment == 'Reid'
assert isclose(result.candidates[c_reid].transfers, -402) assert isclose(result.candidates[c_reid].transfers, -402)
assert isclose(result.candidates[c_evans].transfers, 402) assert isclose(result.candidates[c_evans].transfers, 402)
# Stage 8 # Stage 8
result = counter.step() result = counter.step()
assert result.comment == 'Exclusion of Reid' assert result.stage_kind == 'Exclusion of'
assert result.comment == 'Reid'
assert isclose(result.candidates[c_reid].transfers, -1785) assert isclose(result.candidates[c_reid].transfers, -1785)
assert isclose(result.candidates[c_evans].transfers, 595) assert isclose(result.candidates[c_evans].transfers, 595)
assert isclose(result.candidates[c_thomson].transfers, 1190) assert isclose(result.candidates[c_thomson].transfers, 1190)
@ -139,39 +146,45 @@ def test_prsa1():
# Stage 9 # Stage 9
result = counter.step() result = counter.step()
assert result.comment == 'Exclusion of Evans' assert result.stage_kind == 'Exclusion of'
assert result.comment == 'Evans'
assert isclose(result.candidates[c_evans].transfers, -1000) assert isclose(result.candidates[c_evans].transfers, -1000)
assert isclose(result.candidates[c_thomson].transfers, 1000) assert isclose(result.candidates[c_thomson].transfers, 1000)
# Stage 10 # Stage 10
result = counter.step() result = counter.step()
assert result.comment == 'Exclusion of Evans' assert result.stage_kind == 'Exclusion of'
assert result.comment == 'Evans'
assert isclose(result.candidates[c_evans].transfers, -1234) assert isclose(result.candidates[c_evans].transfers, -1234)
assert isclose(result.exhausted.transfers, 1234) assert isclose(result.exhausted.transfers, 1234)
# Stage 11 # Stage 11
result = counter.step() result = counter.step()
assert result.comment == 'Exclusion of Evans' assert result.stage_kind == 'Exclusion of'
assert result.comment == 'Evans'
assert isclose(result.candidates[c_evans].transfers, -804) assert isclose(result.candidates[c_evans].transfers, -804)
assert isclose(result.candidates[c_thomson].transfers, 402) assert isclose(result.candidates[c_thomson].transfers, 402)
assert isclose(result.candidates[c_white].transfers, 402) assert isclose(result.candidates[c_white].transfers, 402)
# Stage 12 # Stage 12
result = counter.step() result = counter.step()
assert result.comment == 'Exclusion of Evans' assert result.stage_kind == 'Exclusion of'
assert result.comment == 'Evans'
assert isclose(result.candidates[c_evans].transfers, -1785) assert isclose(result.candidates[c_evans].transfers, -1785)
assert isclose(result.candidates[c_white].transfers, 1190) assert isclose(result.candidates[c_white].transfers, 1190)
assert isclose(result.exhausted.transfers, 595) assert isclose(result.exhausted.transfers, 595)
# Stage 13 # Stage 13
result = counter.step() result = counter.step()
assert result.comment == 'Exclusion of Evans' assert result.stage_kind == 'Exclusion of'
assert result.comment == 'Evans'
assert isclose(result.candidates[c_evans].transfers, -402) assert isclose(result.candidates[c_evans].transfers, -402)
assert isclose(result.candidates[c_thomson].transfers, 402) assert isclose(result.candidates[c_thomson].transfers, 402)
# Stage 14 # Stage 14
result = counter.step() result = counter.step()
assert result.comment == 'Exclusion of Evans' assert result.stage_kind == 'Exclusion of'
assert result.comment == 'Evans'
assert isclose(result.candidates[c_evans].transfers, -595) assert isclose(result.candidates[c_evans].transfers, -595)
assert isclose(result.candidates[c_white].transfers, 595) assert isclose(result.candidates[c_white].transfers, 595)
@ -186,19 +199,22 @@ def test_prsa1():
# Stage 15 # Stage 15
result = counter.step() result = counter.step()
assert result.comment == 'Exclusion of Thomson' assert result.stage_kind == 'Exclusion of'
assert result.comment == 'Thomson'
assert isclose(result.candidates[c_thomson].transfers, -5000) assert isclose(result.candidates[c_thomson].transfers, -5000)
assert isclose(result.exhausted.transfers, 5000) assert isclose(result.exhausted.transfers, 5000)
# Stage 16 # Stage 16
result = counter.step() result = counter.step()
assert result.comment == 'Exclusion of Thomson' assert result.stage_kind == 'Exclusion of'
assert result.comment == 'Thomson'
assert isclose(result.candidates[c_thomson].transfers, -2468) assert isclose(result.candidates[c_thomson].transfers, -2468)
assert isclose(result.exhausted.transfers, 2468) assert isclose(result.exhausted.transfers, 2468)
# Stage 17 # Stage 17
result = counter.step() result = counter.step()
assert result.comment == 'Exclusion of Thomson' assert result.stage_kind == 'Exclusion of'
assert result.comment == 'Thomson'
assert isclose(result.candidates[c_thomson].transfers, -1206) assert isclose(result.candidates[c_thomson].transfers, -1206)
assert isclose(result.candidates[c_white].transfers, 1206) assert isclose(result.candidates[c_white].transfers, 1206)