diff --git a/html/index.js b/html/index.js
index 2c1e722..5b51ada 100644
--- a/html/index.js
+++ b/html/index.js
@@ -202,7 +202,8 @@ async function clickCount() {
// Step election
let worker = new Worker('worker.js');
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;
worker.onmessage = function(evt) {
@@ -220,9 +221,22 @@ async function clickCount() {
if (evt.data.type === 'init') {
election = evt.data.election;
- // Comment row
- trComment = document.createElement('tr');
+ // Comment rows
+ trStageNo = document.createElement('tr');
+ trStageNo.classList.add('stage-no');
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);
tblResults.appendChild(trComment);
@@ -233,7 +247,7 @@ async function clickCount() {
elTd = document.createElement('td');
elTd.setAttribute('rowspan', '2');
- elTd.style.borderTop = '1px solid black';
+ elTd.classList.add('bt');
elTd.innerText = candidate;
elTr1.appendChild(elTd);
@@ -251,7 +265,7 @@ async function clickCount() {
elTd = document.createElement('td');
elTd.setAttribute('rowspan', '2');
- elTd.style.borderTop = '1px solid black';
+ elTd.classList.add('bt');
elTd.innerText = 'Exhausted';
trExhausted1.appendChild(elTd);
@@ -268,7 +282,7 @@ async function clickCount() {
elTd = document.createElement('td');
elTd.setAttribute('rowspan', '2');
- elTd.style.borderTop = '1px solid black';
+ elTd.classList.add('bt');
elTd.innerText = 'Loss to fraction';
trLTF1.appendChild(elTd);
@@ -279,7 +293,7 @@ async function clickCount() {
trTotal = document.createElement('tr');
trTotal.classList.add('info');
elTd = document.createElement('td');
- elTd.style.borderTop = '1px solid black';
+ elTd.classList.add('bt');
elTd.innerText = 'Total';
trTotal.appendChild(elTd);
tblResults.appendChild(trTotal);
@@ -288,8 +302,8 @@ async function clickCount() {
trQuota = document.createElement('tr');
trQuota.classList.add('info');
elTd = document.createElement('td');
- elTd.style.borderTop = '1px solid black';
- elTd.style.borderBottom = '1px solid black';
+ elTd.classList.add('bt');
+ elTd.classList.add('bb');
elTd.innerText = 'Quota';
trQuota.appendChild(elTd);
tblResults.appendChild(trQuota);
@@ -299,8 +313,8 @@ async function clickCount() {
trVRE = document.createElement('tr');
trVRE.classList.add('info');
elTd = document.createElement('td');
- elTd.style.borderTop = '1px solid black';
- elTd.style.borderBottom = '1px solid black';
+ elTd.classList.add('bt');
+ elTd.classList.add('bb');
elTd.innerText = 'Vote required for election';
trVRE.appendChild(elTd);
tblResults.appendChild(trVRE);
@@ -336,7 +350,15 @@ async function clickCount() {
// Display results
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);
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) {
elTd.classList.add('elected');
}
- elTd.style.borderTop = '1px solid black';
+ elTd.classList.add('bt');
elTd.innerHTML = ppVotes(countCard.transfers);
elTr1.appendChild(elTd);
@@ -383,7 +405,7 @@ async function clickCount() {
// Display exhausted votes
elTd = document.createElement('td');
elTd.classList.add('count');
- elTd.style.borderTop = '1px solid black';
+ elTd.classList.add('bt');
elTd.innerHTML = ppVotes(result.exhausted.transfers);
trExhausted1.appendChild(elTd);
@@ -395,7 +417,7 @@ async function clickCount() {
// Display loss to fraction
elTd = document.createElement('td');
elTd.classList.add('count');
- elTd.style.borderTop = '1px solid black';
+ elTd.classList.add('bt');
elTd.innerHTML = ppVotes(result.loss_fraction.transfers);
trLTF1.appendChild(elTd);
@@ -413,15 +435,15 @@ async function clickCount() {
// Display total
elTd = document.createElement('td');
elTd.classList.add('count');
- elTd.style.borderTop = '1px solid black';
+ elTd.classList.add('bt');
elTd.innerHTML = ppVotes(result.total);
trTotal.appendChild(elTd);
// Display quota
elTd = document.createElement('td');
elTd.classList.add('count');
- elTd.style.borderTop = '1px solid black';
- elTd.style.borderBottom = '1px solid black';
+ elTd.classList.add('bt');
+ elTd.classList.add('bb');
elTd.innerHTML = ppVotes(result.quota);
trQuota.appendChild(elTd);
@@ -429,8 +451,8 @@ async function clickCount() {
if (result.vote_required_election !== null) {
elTd = document.createElement('td');
elTd.classList.add('count');
- elTd.style.borderTop = '1px solid black';
- elTd.style.borderBottom = '1px solid black';
+ elTd.classList.add('bt');
+ elTd.classList.add('bb');
elTd.innerHTML = ppVotes(result.vote_required_election);
trVRE.appendChild(elTd);
}
@@ -462,11 +484,11 @@ async function clickCount() {
elTd.innerHTML = 'ELECTED ' + countCard.order_elected;
winners.append([candidate, countCard]);
}
- elTd.style.borderTop = '1px solid black';
+ elTd.classList.add('bt');
elTr1.appendChild(elTd);
}
- elTd.style.borderBottom = '1px solid black';
+ elTd.classList.add('bb');
let elP = document.createElement('p');
elP.innerText = 'Count complete. The winning candidates are, in order of election:'
@@ -541,6 +563,7 @@ async function clickCount() {
if (result.indexOf('.') >= 0) {
result = result.substring(0, result.indexOf('.')) + '' + result.substring(result.indexOf('.')) + '';
}
+ result = result.replace('-', '−');
return result;
}
}
diff --git a/html/main.css b/html/main.css
index 033878d..be1024f 100644
--- a/html/main.css
+++ b/html/main.css
@@ -16,14 +16,25 @@
along with this program. If not, see .
*/
+@import url('https://fonts.googleapis.com/css2?family=Source+Sans+Pro&display=swap');
+
html, body {
- font-family: 'Liberation Sans', FreeSans, Helvetica, Arial, sans-serif;
+ font-family: 'Source Sans Pro', sans-serif;
}
body {
padding: 0.5em;
}
+a {
+ color: #1d46c4;
+ text-decoration: none;
+}
+a:hover {
+ color: #1d3da2;
+ text-decoration: underline;
+}
+
/* Menu styling */
.menudiv {
@@ -69,24 +80,41 @@ td.count sup {
font-size: 0.6rem;
top: 0;
}
-.result tr:first-child td {
- vertical-align: bottom;
+tr.stage-no td, tr.stage-kind td, tr.stage-comment td {
+ 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 {
- background-color: #fecfcfff;
+ background-color: #fde2e2;
color-adjust: exact;
-webkit-print-color-adjust: exact;
}
td.elected {
- background-color: #d1fca7ff;
+ background-color: #e0fdc5;
color-adjust: exact;
-webkit-print-color-adjust: exact;
}
tr.info td {
- background-color: #edededff;
+ background-color: #f0f5fb;
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 */
diff --git a/html/worker.js b/html/worker.js
index 914ebf3..79fd78d 100644
--- a/html/worker.js
+++ b/html/worker.js
@@ -145,6 +145,7 @@ function handleException(ex) {
function resultToJS(result) {
return {
'stage': stage,
+ 'stage_kind': result.stage_kind,
'comment': result.comment,
'logs': result.logs,
'candidates': result.candidates.py_items().map(([c, cc]) => [c.py_name, {
diff --git a/pyRCV2/cli/stv.py b/pyRCV2/cli/stv.py
index dee8f5b..79a7650 100644
--- a/pyRCV2/cli/stv.py
+++ b/pyRCV2/cli/stv.py
@@ -61,7 +61,10 @@ 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)')
def print_step(args, stage, result):
- print('{}. {}'.format(stage, result.comment))
+ if result.stage_kind:
+ print('{}. {} {}'.format(stage, result.stage_kind, result.comment))
+ else:
+ print('{}. {}'.format(stage, result.comment))
if result.logs:
print(' '.join(result.logs))
diff --git a/pyRCV2/method/base_stv.py b/pyRCV2/method/base_stv.py
index f29eec5..2d3dd56 100644
--- a/pyRCV2/method/base_stv.py
+++ b/pyRCV2/method/base_stv.py
@@ -99,7 +99,7 @@ class BaseSTVCounter:
self.compute_quota()
self.elect_meeting_quota()
- return self.make_result('First preferences')
+ return self.make_result(None, 'First preferences')
def distribute_first_preferences(self):
"""
@@ -180,6 +180,7 @@ class BaseSTVCounter:
if self.num_elected >= self.election.seats:
__pragma__('opov')
return CountCompleted(
+ None,
'Count complete',
self.logs,
self.candidates,
@@ -193,7 +194,6 @@ class BaseSTVCounter:
# Are there just enough candidates to fill all the seats?
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:
# 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]
@@ -212,7 +212,7 @@ class BaseSTVCounter:
constraints.stabilise_matrix(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):
"""
@@ -301,7 +301,7 @@ class BaseSTVCounter:
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):
"""
@@ -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 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
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)
@@ -344,7 +345,7 @@ class BaseSTVCounter:
else:
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):
"""
@@ -369,13 +370,21 @@ class BaseSTVCounter:
Exclude the lowest ranked hopeful(s)
"""
- candidates_excluded = self.candidates_to_exclude()
+ # 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()
+ if len(candidates_excluded) > 0:
+ if len(candidates_excluded) == 1:
+ self.logs.append('No surpluses to distribute, so ' + candidates_excluded[0][0].name + ' is excluded.')
+ else:
+ 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:
- if len(candidates_excluded) == 1:
- self.logs.append('No surpluses to distribute, so ' + candidates_excluded[0][0].name + ' is excluded.')
- else:
- self.logs.append('No surpluses to distribute, so ' + self.pretty_join([c.name for c, cc in candidates_excluded]) + ' are excluded.')
-
return self.exclude_candidates(candidates_excluded)
def exclude_candidates(self, candidates_excluded):
@@ -434,7 +443,7 @@ class BaseSTVCounter:
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):
"""
@@ -479,13 +488,6 @@ class BaseSTVCounter:
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.sort(key=lambda x: x[1].votes)
@@ -775,9 +777,10 @@ class BaseSTVCounter:
return num
return num.round(self.options['round_tvs'], num.ROUND_DOWN)
- def make_result(self, comment):
+ def make_result(self, stage_kind, comment):
__pragma__('opov')
result = CountStepResult(
+ stage_kind,
comment,
self.logs,
self.candidates,
diff --git a/pyRCV2/method/meek.py b/pyRCV2/method/meek.py
index e9141b7..2bc208e 100644
--- a/pyRCV2/method/meek.py
+++ b/pyRCV2/method/meek.py
@@ -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.elect_meeting_quota()
- return self.make_result('First preferences')
+ return self.make_result(None, 'First preferences')
def distribute_recursively(self, tree, remaining_multiplier):
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
self.elect_meeting_quota()
- return self.make_result('Surpluses distributed')
+ return self.make_result(None, 'Surpluses distributed')
def do_exclusion(self, candidates_excluded):
"""
diff --git a/pyRCV2/model.py b/pyRCV2/model.py
index 50b9e1f..ab4be7b 100644
--- a/pyRCV2/model.py
+++ b/pyRCV2/model.py
@@ -155,7 +155,8 @@ class CountCard:
__pragma__('noopov')
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.logs = logs
@@ -176,7 +177,7 @@ class CountStepResult:
candidates[c] = cc.clone()
__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):
pass
diff --git a/tests/test_aec.py b/tests/test_aec.py
index 029ff45..878c96a 100644
--- a/tests/test_aec.py
+++ b/tests/test_aec.py
@@ -60,7 +60,8 @@ def test_aec_tas19():
result = counter.step()
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):
votes = pyRCV2.numbers.Num(data[j + 2][i])
diff --git a/tests/test_ers97.py b/tests/test_ers97.py
index c346018..27b908d 100644
--- a/tests/test_ers97.py
+++ b/tests/test_ers97.py
@@ -67,7 +67,8 @@ def test_ers97_py():
result = counter.step()
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):
votes = pyRCV2.numbers.Num(data[j + 2][i])
@@ -112,7 +113,8 @@ def test_ers97_js():
assert ctx.eval('result = counter.step();')
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):
ctx.eval('votes = py.pyRCV2.numbers.Num("{}");'.format(data[j + 2][i]))
diff --git a/tests/test_prsa.py b/tests/test_prsa.py
index 1552760..cd560b8 100644
--- a/tests/test_prsa.py
+++ b/tests/test_prsa.py
@@ -69,7 +69,8 @@ def test_prsa1():
# Stage 2
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_grey].votes, 13001)
assert isclose(result.candidates[c_thomson].votes, 7468)
@@ -81,7 +82,8 @@ def test_prsa1():
# Stage 3
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_grey].votes, 13001)
assert isclose(result.candidates[c_thomson].votes, 8674)
@@ -93,7 +95,8 @@ def test_prsa1():
# Stage 4
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_grey].votes, 13001)
assert isclose(result.candidates[c_thomson].votes, 8674)
@@ -105,25 +108,29 @@ def test_prsa1():
# Stage 5
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_white].transfers, 1000)
# Stage 6
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_white].transfers, 617)
# Stage 7
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_evans].transfers, 402)
# Stage 8
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_evans].transfers, 595)
assert isclose(result.candidates[c_thomson].transfers, 1190)
@@ -139,39 +146,45 @@ def test_prsa1():
# Stage 9
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_thomson].transfers, 1000)
# Stage 10
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.exhausted.transfers, 1234)
# Stage 11
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_thomson].transfers, 402)
assert isclose(result.candidates[c_white].transfers, 402)
# Stage 12
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_white].transfers, 1190)
assert isclose(result.exhausted.transfers, 595)
# Stage 13
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_thomson].transfers, 402)
# Stage 14
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_white].transfers, 595)
@@ -186,19 +199,22 @@ def test_prsa1():
# Stage 15
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.exhausted.transfers, 5000)
# Stage 16
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.exhausted.transfers, 2468)
# Stage 17
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_white].transfers, 1206)