Skip to content
Snippets Groups Projects
Commit 47c633a6 authored by Alexis POYEN's avatar Alexis POYEN
Browse files

Merge branch '55-calculate-the-results-for-direct-metropolitan-election' into 'master'

Resolve "Calculate the results for direct metropolitan election"

Closes #55

See merge request apoyen/elections!51
parents 8a5b24b9 5cd3d4a6
No related branches found
No related tags found
1 merge request!51Resolve "Calculate the results for direct metropolitan election"
Pipeline #6200 passed
No preview for this file type
// Imports // Imports
import * as results from "/services/election/calculate-election-generic.js";
export async function mount(where, round) { export async function mount(where, round) {
const resultComponent = new ResultComponent(round); const resultComponent = new ResultComponent(round);
await resultComponent.mount(where); await resultComponent.mount(where);
await resultComponent.calculateResults();
} }
class ResultComponent { class ResultComponent {
...@@ -29,15 +31,15 @@ class ResultComponent { ...@@ -29,15 +31,15 @@ class ResultComponent {
</div> </div>
<div class="control filter"> <div class="control filter">
<label class="radio"> <label class="radio">
<input type="radio" name="answer" checked /> <input type="radio" name="filter" value="partial" checked />
Partiel Partiel
</label> </label>
<label class="radio"> <label class="radio">
<input type="radio" name="answer" /> <input type="radio" name="filter" value="completed" />
Complété Complété
</label> </label>
<label class="radio"> <label class="radio">
<input type="radio" name="answer" /> <input type="radio" name="filter" value="validated" />
Validé Validé
</label> </label>
</div> </div>
...@@ -100,6 +102,7 @@ class ResultComponent { ...@@ -100,6 +102,7 @@ class ResultComponent {
`; `;
this.handleDom(); this.handleDom();
document.getElementById("areas").click(); document.getElementById("areas").click();
this.calculator = await results.mountCalculator(this.round);
} }
handleDom() { handleDom() {
...@@ -126,6 +129,13 @@ class ResultComponent { ...@@ -126,6 +129,13 @@ class ResultComponent {
.addEventListener("click", function () { .addEventListener("click", function () {
resultHandler.zoomResults(); resultHandler.zoomResults();
}); });
let radioButtons = document.getElementsByName("filter");
for (var i = 0; i < radioButtons.length; i++) {
radioButtons[i].addEventListener("click", (e) => {
this.calculateResults();
});
}
} }
zoomMap() { zoomMap() {
...@@ -152,7 +162,7 @@ class ResultComponent { ...@@ -152,7 +162,7 @@ class ResultComponent {
}); });
} }
zoomResults(){ zoomResults() {
let resultHandler = this; let resultHandler = this;
document.getElementById("results-section").parentElement.className = document.getElementById("results-section").parentElement.className =
"column is-full"; "column is-full";
...@@ -179,4 +189,9 @@ class ResultComponent { ...@@ -179,4 +189,9 @@ class ResultComponent {
this.handleDom(); this.handleDom();
} }
async calculateResults() {
let filter = document.querySelector('input[name="filter"]:checked').value;
await this.calculator.calculateResults(filter);
}
} }
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;
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment