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(); return 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 ); currentParty.Percentage = Number( (currentParty.VoiceNumber / this.stats.VotesExpressed) * 100 ).toFixed(2); } }); 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; let lastDate = new Date(area.Sections[0].DateCompletion); area.Sections.forEach((section) => { if (lastDate - new Date(section.DateCompletion) < 0) lastDate = new Date(section.DateCompletion); }); area.DateCompletion = lastDate; 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; } 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 area; } break; case "validated": flag = true; deskRounds.forEach((deskRound) => { if (!deskRound.Validated) flag = false; }); if (flag) { area.status = "validated"; } else { area.status = "not validated"; return area; } } candidateListToKeep.forEach((candidateList) => { candidateList.VoiceNumber = candidateList.Votes.reduce( (voiceNumber, vote) => { return voiceNumber + vote.VoiceNumber; }, 0 ); candidateList.Percentage = Number( (candidateList.VoiceNumber / area.stats.VotesExpressed) * 100 ).toFixed(2); }); 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]); } } let lastDate = new Date(deskRounds[0].DateCompletion); deskRounds.forEach((desk) => { if (lastDate - new Date(desk.DateCompletion) < 0) lastDate = new Date(desk.DateCompletion); }); section.DateCompletion = lastDate; 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; } 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 section; } break; case "validated": flag = true; deskRounds.forEach((deskRound) => { if (!deskRound.Validated) flag = false; }); if (flag) { section.status = "validated"; } else { section.status = "not validated"; return section; } } 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 = Number( (candidateList.VoiceNumber / section.stats.VotesExpressed) * 100 ).toFixed(2); }); 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; } }