A fork of Gitea (see branch `mj`) adding Majority Judgment Polls 𐄷 over Issues and Merge Requests. https://git.mieuxvoter.fr
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

219 lines
5.6 KiB

// Copyright 2020 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package models
import (
"code.gitea.io/gitea/modules/timeutil"
)
// ____ _ _____ _ _
// / ___|_ __ __ _ __| | ___ |_ _|_ _| | |_ _
// | | _| '__/ _` |/ _` |/ _ \ | |/ _` | | | | | |
// | |_| | | | (_| | (_| | __/ | | (_| | | | |_| |
// \____|_| \__,_|\__,_|\___| |_|\__,_|_|_|\__, |
// |___/
type PollCandidateGradeTally struct {
Grade uint8
Amount uint64
CreatedUnix timeutil.TimeStamp
}
// ____ _ _ _ _ _____ _ _
// / ___|__ _ _ __ __| (_) __| | __ _| |_ ___ |_ _|_ _| | |_ _
// | | / _` | '_ \ / _` | |/ _` |/ _` | __/ _ \ | |/ _` | | | | | |
// | |__| (_| | | | | (_| | | (_| | (_| | || __/ | | (_| | | | |_| |
// \____\__,_|_| |_|\__,_|_|\__,_|\__,_|\__\___| |_|\__,_|_|_|\__, |
// |___/
type PollCandidateTally struct {
Poll *Poll
CandidateID int64 // Issue Index (or internal candidate index, later on)
Grades []*PollCandidateGradeTally // Sorted by grade (0 == REJECT)
JudgmentsAmount uint64
CreatedUnix timeutil.TimeStamp
}
func (pct *PollCandidateTally) Copy() (_ *PollCandidateTally) {
grades := make([]*PollCandidateGradeTally, 0, 8)
for _, grade := range pct.Grades {
grades = append(grades, &PollCandidateGradeTally{
Grade: grade.Grade,
Amount: grade.Amount,
CreatedUnix: grade.CreatedUnix,
})
}
return &PollCandidateTally{
Poll: pct.Poll,
CandidateID: pct.CandidateID,
Grades: grades,
JudgmentsAmount: pct.JudgmentsAmount,
CreatedUnix: pct.CreatedUnix,
}
}
func (pct *PollCandidateTally) GetMedian() (_ uint8) {
if 0 == pct.JudgmentsAmount {
return uint8(0)
//return 0 // to test
}
adjustedTotal := pct.JudgmentsAmount - 1
//if opts.UseHighMedian {
// adjustedTotal := pct.JudgmentsAmount + 1
//}
medianIndex := adjustedTotal / 2 // Euclidean div
cursorIndex := uint64(0)
for _, grade := range pct.Grades {
if 0 == grade.Amount {
continue
}
startIndex := cursorIndex
cursorIndex += grade.Amount
endIndex := cursorIndex
if (startIndex <= medianIndex) && (medianIndex < endIndex) {
return grade.Grade
}
}
println("warning: GetMedian defaulting to 0")
return uint8(0)
}
func (pct *PollCandidateTally) GetBiggestGroup(aroundGrade uint8) (groupSize int, groupSign int, groupGrade uint8) {
belowGroupSize := 0
belowGroupSign := -1
belowGroupGrade := uint8(0)
aboveGroupSize := 0
aboveGroupSign := 1
aboveGroupGrade := uint8(0)
for k, _ := range pct.Poll.GetGradationList() {
grade := uint8(k)
gradeAmount := int(pct.Grades[grade].Amount)
if 0 == gradeAmount {
continue
}
if grade < aroundGrade {
belowGroupSize += gradeAmount
belowGroupGrade = grade
}
if grade > aroundGrade {
aboveGroupSize += gradeAmount
if 0 == aboveGroupGrade {
aboveGroupGrade = grade
}
}
}
// /!. Assumption of LOW median with `>` /!.
if aboveGroupSize > belowGroupSize {
return aboveGroupSize, aboveGroupSign, aboveGroupGrade
}
return belowGroupSize, belowGroupSign, belowGroupGrade
}
func (pct *PollCandidateTally) RegradeJudgments(fromGrade uint8, toGrade uint8) {
if toGrade == fromGrade {
return
}
pct.Grades[toGrade].Amount += pct.Grades[fromGrade].Amount
pct.Grades[fromGrade].Amount = 0
}
// _____ _ _
// |_ _|_ _| | |_ _
// | |/ _` | | | | | |
// | | (_| | | | |_| |
// |_|\__,_|_|_|\__, |
// |___/
type PollTally struct {
Poll *Poll
MaxJudgmentsAmount uint64 // per candidate, will help including default grade 0=TO_REJECT
Candidates []*PollCandidateTally
CreatedUnix timeutil.TimeStamp
}
// _____ _ _ _
// |_ _|_ _| | (_) ___ _ __
// | |/ _` | | | |/ _ \ '__|
// | | (_| | | | | __/ |
// |_|\__,_|_|_|_|\___|_|
//
type PollTallier interface {
Tally(poll *Poll) (tally *PollTally, err error)
}
// _ _ _
// | \ | | __ _(_)_ _____
// | \| |/ _` | \ \ / / _ \
// | |\ | (_| | |\ V / __/
// |_| \_|\__,_|_| \_/ \___|
//
type PollNaiveTallier struct{}
func (tallier *PollNaiveTallier) Tally(poll *Poll) (_ *PollTally, err error) {
gradation := poll.GetGradationList()
candidatesIDs, errG := poll.GetCandidatesIDs()
if nil != errG {
return nil, errG
}
candidates := make([]*PollCandidateTally, 0, 64)
maximumAmount := uint64(0)
for _, candidateID := range candidatesIDs {
grades := make([]*PollCandidateGradeTally, 0, 8)
judgmentsAmount := uint64(0)
for gradeInt, _ := range gradation {
grade := uint8(gradeInt)
amount, errC := poll.CountGrades(candidateID, grade)
if nil != errC {
return nil, errC
}
judgmentsAmount += amount
grades = append(grades, &PollCandidateGradeTally{
Grade: grade,
Amount: amount,
CreatedUnix: timeutil.TimeStampNow(),
})
}
//maximumAmount = util.Max(judgmentsAmount, maximumAmount)
if maximumAmount < judgmentsAmount {
maximumAmount = judgmentsAmount
}
candidates = append(candidates, &PollCandidateTally{
Poll: poll,
CandidateID: candidateID,
Grades: grades,
JudgmentsAmount: judgmentsAmount,
CreatedUnix: timeutil.TimeStampNow(),
})
}
tally := &PollTally{
Poll: poll,
MaxJudgmentsAmount: maximumAmount,
Candidates: candidates,
CreatedUnix: timeutil.TimeStampNow(),
}
return tally, nil
}