Skip to content
Snippets Groups Projects
calculate-election-generic.js 14 KiB
Newer Older
  • Learn to ignore specific revisions
  • 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";
    
            }
        }
    
        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;
      }
    }