From 5cd3d4a664ab19c2de2c5531e4565a23186dc331 Mon Sep 17 00:00:00 2001 From: Alexis POYEN <apoyen@grandlyon.com> Date: Fri, 26 Jun 2020 11:14:45 +0200 Subject: [PATCH] Resolve "Calculate the results for direct metropolitan election" --- data/test.db | Bin 73728 -> 73728 bytes .../visualization/results-section.js | 23 +- .../election/calculate-election-generic.js | 424 ++++++++++++++++++ 3 files changed, 443 insertions(+), 4 deletions(-) create mode 100644 web/services/election/calculate-election-generic.js diff --git a/data/test.db b/data/test.db index e4f37d29268ba4499279d30c1322d4f75cac4977..2f2f142d93f55b5661c089395d795c6bd40d1d10 100644 GIT binary patch literal 73728 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 delta 897 zcmYjPOK1~O6rGv;I!R{UBsNBqhE5U0Qd1{s&8HPgo7%dNrZI?!LWu2Xrj0Xonk+(5 zTXbOsvGJmt+K(0qB7W#BL`t!xZn{XKAk@9M68sf(<HE@_)I8qfzQ?)eoO>^Ca!o(E zrk`=!;uwb6s|QzOWBzcPHIB7L)+zPH5KH+{kI~2xOZZ-}F}GnKKB2G-fz$Z3itjFE ziMXRPPfai;r_+hwI1O|7w&FEy9demb3`alFXY>gvXdOL3kI^)Wp&SY;Hr9<G+{Lkb zc6D|xG_7kwLrHO17*1yR@qjYjwcQA36CUfyoFuXlUd+h637#a{VI@j(p^mmttFMjo z2g0G?(H73%!EsvRfFuu-l|Bq~H--u*O8=qf>2Afw9HEWp3?vqdL7(Ua;hF``i#C`M zd*G$$f$!q6iZIxudN?6<YIi}Zr!-MNe+DP9cpP3xtrdi>+P8&+;b5R$`6VINt$La* zn;5!}n(0+KMtz}j6m5NE4Oli+_vaR6bGCt~8!?zwA&i6%vko{u@4EMpRD&H7bNfld zh`t6~nRmhUyCkesQT>Rn2K6mysNK)CwugQGpjt|G44RZ~gs4d_>?A{Bp<?8!6YwfH zHRpgUvy~z=p<pQJ5BNguJM*jvEidA%L+#8+B`^f&ApMFCQza^4-BNpcXT@*j^EZjh zMtD<l!_}e(-ma1`S+ujp!IY2|#>RLyA`PYZjGyB;FXvOYV4$#Q6jn=4_*!&pgl|RW zfT3UBczQ{ab8N4a9jYZ{^;BhIcqgot>LFFCn3zB))wk#el9^O8E%59hFUx}H?HAG) zc)7+bqub$zRjyCv8R28e;nelXNpYhf3FGX5wDBzM?a5xe$Y-FT+^gk8%8n+YN6v~8 z+r!IZQXc+a{wS^~lFh231C(-u##k;p=(zfM)gpNIR958E%I`AGDf;(+(QAgmf1vy# F^AA|N5k>$2 diff --git a/web/components/visualization/results-section.js b/web/components/visualization/results-section.js index cbee50f..b27d99c 100644 --- a/web/components/visualization/results-section.js +++ b/web/components/visualization/results-section.js @@ -1,8 +1,10 @@ // Imports +import * as results from "/services/election/calculate-election-generic.js"; export async function mount(where, round) { const resultComponent = new ResultComponent(round); await resultComponent.mount(where); + await resultComponent.calculateResults(); } class ResultComponent { @@ -29,15 +31,15 @@ class ResultComponent { </div> <div class="control filter"> <label class="radio"> - <input type="radio" name="answer" checked /> + <input type="radio" name="filter" value="partial" checked /> Partiel </label> <label class="radio"> - <input type="radio" name="answer" /> + <input type="radio" name="filter" value="completed" /> Complété </label> <label class="radio"> - <input type="radio" name="answer" /> + <input type="radio" name="filter" value="validated" /> Validé </label> </div> @@ -100,6 +102,7 @@ class ResultComponent { `; this.handleDom(); document.getElementById("areas").click(); + this.calculator = await results.mountCalculator(this.round); } handleDom() { @@ -126,6 +129,13 @@ class ResultComponent { .addEventListener("click", function () { resultHandler.zoomResults(); }); + + let radioButtons = document.getElementsByName("filter"); + for (var i = 0; i < radioButtons.length; i++) { + radioButtons[i].addEventListener("click", (e) => { + this.calculateResults(); + }); + } } zoomMap() { @@ -152,7 +162,7 @@ class ResultComponent { }); } - zoomResults(){ + zoomResults() { let resultHandler = this; document.getElementById("results-section").parentElement.className = "column is-full"; @@ -179,4 +189,9 @@ class ResultComponent { this.handleDom(); } + + async calculateResults() { + let filter = document.querySelector('input[name="filter"]:checked').value; + await this.calculator.calculateResults(filter); + } } diff --git a/web/services/election/calculate-election-generic.js b/web/services/election/calculate-election-generic.js new file mode 100644 index 0000000..5a6ccb6 --- /dev/null +++ b/web/services/election/calculate-election-generic.js @@ -0,0 +1,424 @@ +import * as Auth from "/services/auth/auth.js"; +import * as ElectionModel from "/services/model/election-model.js"; +import * as RoundModel from "/services/model/round-model.js"; +import * as AreaModel from "/services/model/area-model.js"; +import * as SectionModel from "/services/model/section-model.js"; +import * as DeskModel from "/services/model/desk-model.js"; +import * as PartyModel from "/services/model/party-model.js"; +import * as DeskRoundModel from "/services/model/deskRound-model.js"; +import * as VoteModel from "/services/model/vote-model.js"; +import * as CandidateListModel from "/services/model/candidateList-model.js"; + +export async function mountCalculator(round) { + const directMetropolitanCalculator = new DirectMetropolitanCalculator(round); + await directMetropolitanCalculator.initModel(); + return directMetropolitanCalculator; +} + +class DirectMetropolitanCalculator { + constructor(round, filter) { + this.round = round; + this.ElectionModel = ElectionModel.getElectionModel(); + this.RoundModel = RoundModel.getRoundModel(); + this.AreaModel = AreaModel.getAreaModel(); + this.SectionModel = SectionModel.getSectionModel(); + this.DeskModel = DeskModel.getDeskModel(); + this.PartyModel = PartyModel.getPartyModel(); + this.DeskRoundModel = DeskRoundModel.getDeskRoundModel(); + this.VoteModel = VoteModel.getVoteModel(); + this.CandidateListModel = CandidateListModel.getCandidateListModel(); + } + + async initModel() { + this.ElectionModel.current_user = await Auth.GetUser(); + this.RoundModel.current_user = await Auth.GetUser(); + this.AreaModel.current_user = await Auth.GetUser(); + this.SectionModel.current_user = await Auth.GetUser(); + this.DeskModel.current_user = await Auth.GetUser(); + this.PartyModel.current_user = await Auth.GetUser(); + this.DeskRoundModel.current_user = await Auth.GetUser(); + this.VoteModel.current_user = await Auth.GetUser(); + this.CandidateListModel.current_user = await Auth.GetUser(); + } + + async calculateResults(filter) { + this.CandidateListModel.refreshCandidateLists(); + let calculator = this; + this.filter = filter; + this.deskRounds = await this.DeskRoundModel.getDeskRounds(); + this.deskRounds = this.deskRounds.filter(function (deskRound) { + return deskRound.RoundID === calculator.round.ID; + }); + this.candidateLists = await this.CandidateListModel.getCandidateLists(); + this.candidateLists = this.candidateLists.filter((candidateList) => { + return candidateList.RoundID === calculator.round.ID; + }); + + this.stats = await this.calculateStats(this.deskRounds); + + let flag = true; + switch (this.filter) { + case "partial": + if (this.stats.VotesExpressed === 0) { + this.status = "no_results"; + } else { + this.roundResults = await this.calculateRoundResults(); + this.status = "partial"; + } + break; + case "completed": + flag = true; + this.deskRounds.forEach((deskRound) => { + if (!deskRound.Completed) flag = false; + }); + if (flag) { + this.roundResults = await this.calculateRoundResults(); + this.stats = await this.calculateStats(this.deskRounds); + this.status = "completed"; + } else { + this.roundResults = null; + this.stats = null; + this.status = "incompleted"; + } + break; + case "validated": + flag = true; + this.deskRounds.forEach((deskRound) => { + if (!deskRound.Validated) flag = false; + }); + if (flag) { + this.roundResults = await this.calculateRoundResults(); + this.stats = await this.calculateStats(this.deskRounds); + this.status = "validated"; + } else { + this.roundResults = null; + this.stats = null; + this.status = "not validated"; + } + } + + this.areasResults = await this.calculateAreasResults(); + console.log(this); + } + + async calculateRoundResults() { + let partiesIDToKeep = []; + this.candidateLists.forEach((candidateList) => { + partiesIDToKeep.push(candidateList.PartyID); + }); + partiesIDToKeep = partiesIDToKeep.filter(function (item, index) { + return partiesIDToKeep.indexOf(item) >= index; + }); + + let parties = await this.PartyModel.getParties(); + parties = parties.filter((party) => partiesIDToKeep.includes(party.ID)); + parties.forEach((party) => { + party.VoiceNumber = 0; + let currentParty = party; + this.candidateLists.forEach((candidateList) => { + if (candidateList.PartyID == currentParty.ID) { + currentParty.VoiceNumber = candidateList.Votes.reduce( + (voiceNumber, vote) => { + return voiceNumber + vote.VoiceNumber; + }, + currentParty.VoiceNumber + ); + } + }); + party = currentParty; + }); + parties.sort((a, b) => { + return b.VoiceNumber - a.VoiceNumber; + }); + return parties; + } + + async calculateAreasResults() { + let calculator = this; + this.AreaModel.refreshAreas(); + let areas = await this.AreaModel.getAreas(); + areas = areas.filter(function (area) { + return area.ElectionID == calculator.round.ElectionID; + }); + + let areasCalculated = []; + for (let i in areas) { + areasCalculated.push(await this.calculateAreaResults(areas[i])); + } + areas = areasCalculated; + return areas; + } + + async calculateAreaResults(area) { + let deskRounds = []; + for (let i in area.Sections) { + let section = await this.SectionModel.getSection(area.Sections[i].ID); + for (let j in section.Desks) { + for (let k in this.deskRounds) { + if (section.Desks[j].ID == this.deskRounds[k].DeskID) + deskRounds.push(this.deskRounds[k]); + } + } + } + + let candidateListToKeep = this.candidateLists.filter((candidateList) => { + return candidateList.AreaID === area.ID; + }); + + let sections = []; + for (let i in area.Sections) { + sections.push( + await this.calculateSectionResults( + area.Sections[i], + candidateListToKeep.map((a) => ({ ...a })) + ) + ); + } + area.Sections = sections; + + area.stats = await this.calculateStats(deskRounds); + + let flag = true; + switch (this.filter) { + case "partial": + if (area.stats.VotesExpressed === 0) { + area.status = "no_results"; + return; + } + area.status = "partial"; + break; + case "completed": + flag = true; + deskRounds.forEach((deskRound) => { + if (!deskRound.Completed) flag = false; + }); + if (flag) { + area.status = "completed"; + } else { + area.status = "incompleted"; + return; + } + break; + case "validated": + flag = true; + deskRounds.forEach((deskRound) => { + if (!deskRound.Validated) flag = false; + }); + if (flag) { + area.status = "validated"; + } else { + area.status = "not validated"; + return; + } + } + + candidateListToKeep.forEach((candidateList) => { + candidateList.VoiceNumber = candidateList.Votes.reduce( + (voiceNumber, vote) => { + return voiceNumber + vote.VoiceNumber; + }, + 0 + ); + candidateList.Percentage = + (candidateList.VoiceNumber / area.stats.VotesExpressed) * 100; + }); + area.candidateLists = candidateListToKeep; + area.candidateLists.sort((a, b) => { + return b.VoiceNumber - a.VoiceNumber; + }); + + area.Electeds = this.getElecteds(area); + + area.candidateLists.sort((a, b) => { + return b.VoiceNumber - a.VoiceNumber; + }); + + return area; + } + + async calculateSectionResults(section, candidateLists) { + section = await this.SectionModel.getSection(section.ID); + let deskRounds = []; + for (let i in section.Desks) { + for (let j in this.deskRounds) { + if (section.Desks[i].ID == this.deskRounds[j].DeskID) + deskRounds.push(this.deskRounds[j]); + } + } + + section.stats = await this.calculateStats(deskRounds); + let flag = true; + switch (this.filter) { + case "partial": + if (section.stats.VotesExpressed === 0) { + section.status = "no_results"; + return; + } + section.status = "partial"; + break; + case "completed": + flag = true; + deskRounds.forEach((deskRound) => { + if (!deskRound.Completed) flag = false; + }); + if (flag) { + section.status = "completed"; + } else { + section.status = "incompleted"; + return; + } + break; + case "validated": + flag = true; + deskRounds.forEach((deskRound) => { + if (!deskRound.Validated) flag = false; + }); + if (flag) { + section.status = "validated"; + } else { + section.status = "not validated"; + return; + } + } + + candidateLists.forEach((candidateList) => { + candidateList.Votes = candidateList.Votes.filter((vote) => { + return deskRounds + .map((deskRound) => deskRound.ID) + .includes(vote.DeskRoundID); + }); + candidateList.VoiceNumber = candidateList.Votes.reduce( + (voiceNumber, vote) => { + return voiceNumber + vote.VoiceNumber; + }, + 0 + ); + candidateList.Percentage = + (candidateList.VoiceNumber / section.stats.VotesExpressed) * 100; + }); + section.candidateLists = candidateLists; + section.candidateLists.sort((a, b) => { + return b.VoiceNumber - a.VoiceNumber; + }); + return section; + } + + async calculateStats(deskRounds) { + let subscribed = 0; + let blank = 0; + let nullVote = 0; + let totalVotes = 0; + let VotesExpressed = 0; + + for (let i in deskRounds) { + let desk = await this.DeskModel.getDesk(deskRounds[i].DeskID); + subscribed += desk.Subscribed; + deskRounds[i].Votes.forEach((vote) => { + totalVotes += vote.VoiceNumber; + if (vote.Blank) blank += vote.VoiceNumber; + else if (vote.NullVote) nullVote += vote.VoiceNumber; + else VotesExpressed += vote.VoiceNumber; + }); + } + return { + Abstention: Number( + ((subscribed - totalVotes) / subscribed) * 100 + ).toFixed(2), + Blank: Number((blank / totalVotes) * 100).toFixed(2), + NullVote: Number((nullVote / totalVotes) * 100).toFixed(2), + VotesExpressed: VotesExpressed, + }; + } + + getElecteds(area) { + let electeds = []; + + // order candidates by rank and remove refused or removed candidates + area.candidateLists.forEach((candidateList) => { + candidateList.Candidates.sort(function (a, b) { + return a.Rank - b.Rank; + }); + for (let i = 0; i < candidateList.Candidates.length; i++) { + if ( + candidateList.Candidates[i].Refused || + candidateList.Candidates[i].Removed + ) { + candidateList.Candidates.splice(i, 1); + } + } + }); + + // première étape + let seatForFirst = parseInt(area.SeatNumber / 2); + if ((parseInt(area.seatNumber) / 2) % 2 != 0) seatForFirst += 1; + electeds = electeds.concat( + area.candidateLists[0].Candidates.splice(0, seatForFirst) + ); + + // deuxième étape + let leftSeats = area.SeatNumber - seatForFirst; + let electoralQuotien = area.stats.VotesExpressed / leftSeats; + + area.candidateLists.forEach((candidateList) => { + let seatsAttributed = parseInt( + candidateList.VoiceNumber / electoralQuotien + ); + candidateList.SeatsAttributed = seatsAttributed; + leftSeats -= seatsAttributed; + if (seatsAttributed > 0) { + electeds = electeds.concat( + candidateList.Candidates.splice(0, seatsAttributed) + ); + } + }); + + // //troisème étape + var day = new Date(); + while (leftSeats > 0) { + area.candidateLists.forEach((candidateList) => { + candidateList.Average = + candidateList.VoiceNumber / + (parseInt(candidateList.SeatsAttributed) + 1); + }); + area.candidateLists.sort(function (a, b) { + return b.Average - a.Average; + }); + if (area.candidateLists[0].Average === area.candidateLists[1].Average) { + if (area.candidateLists[1].vote > area.candidateLists[0].vote) + [area.candidateLists[0], area.candidateLists[1]] = [ + area.candidateLists[1], + area.candidateLists[0], + ]; + if (area.candidateLists[0].vote == area.candidateLists[1].vote) { + if ( + area.candidateLists[0].Candidates[0].Birthdate == "" || + area.candidateLists[1].Candidates[0].Birthdate == "" + ) { + area.errorAgeAverage = true; + } + ageCandidate1 = ageCount( + day, + new Date(area.candidateLists[0].Candidates[0].Birthdate) + ); + ageCandidate2 = ageCount( + day, + new Date(area.candidateLists[1].Candidates[0].Birthdate) + ); + if (ageCandidate2 > ageCandidate1) { + [area.candidateLists[0], area.candidateLists[1]] = [ + area.candidateLists[1], + area.candidateLists[0], + ]; + } + } + } + electeds = electeds.concat( + area.candidateLists[0].Candidates.splice(0, 1) + ); + area.candidateLists[0].SeatsAttributed += 1; + leftSeats -= 1; + } + + return electeds; + } +} -- GitLab