/* pyRCV2: Preferential vote counting Copyright © 2020 Lee Yingtong Li (RunasSudo) This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ importScripts('vendor/BigInt_BigRat-a5f89e2.min.js', 'vendor/big-6.0.0.min.js', 'vendor/sjcl-1.0.8.min.js', 'bundle.js'); let stage, result, counter, ppDPs, tiesPrompt; onmessage = function(evt) { if (evt.data.type === 'init') { // Set settings if (evt.data.data.numbers === 'native') { py.pyRCV2.numbers.set_numclass(py.pyRCV2.numbers.Native); } if (evt.data.data.numbers === 'rational') { py.pyRCV2.numbers.set_numclass(py.pyRCV2.numbers.Rational); } if (evt.data.data.numbers === 'fixed') { py.pyRCV2.numbers.set_numclass(py.pyRCV2.numbers.Fixed); py.pyRCV2.numbers.set_dps(evt.data.data.fixedDPs); } if (evt.data.data.numbers === 'gfixed') { py.pyRCV2.numbers.set_numclass(py.pyRCV2.numbers.FixedGuarded); py.pyRCV2.numbers.set_dps(evt.data.data.fixedDPs); } ppDPs = evt.data.data.ppDPs; let election = py.pyRCV2.blt.readBLT(evt.data.data.data); let total_ballots = py.pyRCV2.numbers.Num(0); for (let b of election.ballots) { total_ballots.__iadd__(b.value); } // Create counter if (evt.data.data.transfers === 'uig') { counter = py.pyRCV2.method.gregory.UIGSTVCounter(election, evt.data.data.options); } else if (evt.data.data.transfers === 'eg') { counter = py.pyRCV2.method.gregory.EGSTVCounter(election, evt.data.data.options); } else if (evt.data.data.transfers === 'meek') { counter = py.pyRCV2.method.meek.MeekSTVCounter(election, evt.data.data.options); } else { counter = py.pyRCV2.method.gregory.WIGSTVCounter(election, evt.data.data.options); } if (evt.data.data.options['ties'] === 'backwards_random') { counter.options['ties'] = [py.pyRCV2.ties.TiesBackwards(counter), py.pyRCV2.ties.TiesRandom(evt.data.data.seed)]; } else if (evt.data.data.options['ties'] === 'forwards_random') { counter.options['ties'] = [py.pyRCV2.ties.TiesForwards(counter), py.pyRCV2.ties.TiesRandom(evt.data.data.seed)]; } else if (evt.data.data.options['ties'] === 'random') { counter.options['ties'] = [py.pyRCV2.ties.TiesRandom(evt.data.data.seed)]; } else if (evt.data.data.options['ties'] === 'prompt') { tiesPrompt = py.pyRCV2.ties.TiesPrompt(); counter.options['ties'] = [tiesPrompt]; } postMessage({ 'type': 'init', 'election': { 'name': election.py_name, 'candidates': election.candidates.map(c => c.py_name), 'seats': election.seats, }, 'total_ballots': total_ballots.pp(0), 'options': counter.describe_options(), }); // Reset stage = 1; try { result = counter.reset(); } catch (ex) { handleException(ex); return; } if (py.isinstance(result, py.pyRCV2.model.CountCompleted)) { postMessage({'type': 'done', 'result': resultToJS(result)}); return; } else { postMessage({'type': 'result', 'result': resultToJS(result)}); } stepElection(); } else if (evt.data.type === 'require_input') { // Data received from the user tiesPrompt.buffer = evt.data.input; stepElection(); } } function stepElection() { while (true) { try { result = counter.step(); } catch (ex) { handleException(ex); return; } stage += 1; if (py.isinstance(result, py.pyRCV2.model.CountCompleted)) { postMessage({'type': 'done', 'result': resultToJS(result)}); break; } else { postMessage({'type': 'result', 'result': resultToJS(result)}); } } } function handleException(ex) { if (py.isinstance(ex, py.pyRCV2.ties.RequireInput)) { // Signals we require input to break a tie postMessage({'type': 'require_input', 'message': ex.message}); } else if (py.isinstance(ex, py.pyRCV2.method.base_stv.STVException)) { console.error(ex); postMessage({'type': 'stv_exception', 'message': ex.message}); } else { console.error(ex); throw ex; } } function resultToJS(result) { return { 'stage': stage, 'comment': result.comment, 'logs': result.logs, 'candidates': result.candidates.py_items().map(([c, cc]) => [c.py_name, { 'transfers': cc.transfers.pp(ppDPs), 'votes': cc.votes.pp(ppDPs), 'state': cc.state, 'order_elected': cc.order_elected, }]), 'exhausted': { 'transfers': result.exhausted.transfers.pp(ppDPs), 'votes': result.exhausted.votes.pp(ppDPs) }, 'loss_fraction': { 'transfers': result.loss_fraction.transfers.pp(ppDPs), 'votes': result.loss_fraction.votes.pp(ppDPs) }, 'total': result.total.pp(ppDPs), 'quota': result.quota.pp(ppDPs), 'vote_required_election': result.vote_required_election === null ? null : result.vote_required_election.pp(ppDPs), }; }