Visual improvements to HTML/JS interface
This commit is contained in:
parent
5c185b386c
commit
1ad1684e67
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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 */
|
||||||
|
|
||||||
|
@ -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, {
|
||||||
|
@ -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)')
|
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):
|
||||||
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:
|
if result.logs:
|
||||||
print(' '.join(result.logs))
|
print(' '.join(result.logs))
|
||||||
|
@ -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,13 +370,21 @@ class BaseSTVCounter:
|
|||||||
Exclude the lowest ranked hopeful(s)
|
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) > 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)
|
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,
|
||||||
|
@ -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):
|
||||||
"""
|
"""
|
||||||
|
@ -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
|
||||||
|
@ -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])
|
||||||
|
@ -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]))
|
||||||
|
@ -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)
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user