A Golang module to rank candidates using Majority Judgment, using a score-based algorithm for performance and scalability
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.
Go to file
Dominique Merle 45144cc0a3
fix: change the lib name, since we can't really change the repo name
3 years ago
.github/workflows chore(ci): add test and coverage jobs 3 years ago
docs docs 3 years ago
.tokeignore chore(config): configure tokei to ignore some files in the lines count 3 years ago
LICENSE.md fix(docs): perfunctory and apparently mandatory copyright notice 3 years ago
README.md feat: add static and median default grade strategies 3 years ago
analysis.go refacto: make things more explicit at the expense of a bool 3 years ago
analysis_test.go feat: support `favorContestation == false` in the Analysis 3 years ago
deliberator.go refacto: move the DeliberatorInterface around 3 years ago
go.mod fix: change the lib name, since we can't really change the repo name 3 years ago
go.sum chore: go mod tidy 3 years ago
majorityjudgment.go refacto: move the DeliberatorInterface around 3 years ago
majorityjudgment_test.go test: structure a test with a provider, to dry things up 3 years ago
result.go feat: output the sorted proposals as well, for convenience 3 years ago
tally.go feat: add static and median default grade strategies 3 years ago
tally_test.go feat: add static and median default grade strategies 3 years ago

README.md

Majority Judgment for Golang

MIT Release Build Status Coverage Code Quality LoC Discord Chat https://discord.gg/rAAQG9S

WORK IN PROGRESS

  • Basic Working Implementation
  • Balancers (static and median)
  • Decide on integer types
  • Clean up and Release

A Golang module to deliberate using Majority Judgment.

It leverages a score-based algorithm, for performance and scalability.

Supports billions of judgments and thousands of proposals per poll, if need be.

Installation

NOT AVAILABLE YET

go get -u github.com/mieuxvoter/judgment

Usage

Say you have the following tally:

Example of a merit profile

You can compute out the majority judgment rank of each proposal like so:


package main

import (
	"fmt"
	"log"

	"github.com/mieuxvoter/judgment"
)

func main() {

    poll := &(judgment.PollTally{
        AmountOfJudges: 10,
        Proposals: []*judgment.ProposalTally{
            {Tally: []uint64{2, 2, 2, 2, 2}}, // Proposal A   Amount of judgments received for each grade,
            {Tally: []uint64{2, 1, 1, 1, 5}}, // Proposal B   from "worst" grade to "best" grade.
            {Tally: []uint64{2, 1, 1, 2, 4}}, // Proposal C   Make sure all tallies are balanced, that is they
            {Tally: []uint64{2, 1, 5, 0, 2}}, // Proposal D   hold the same total amount of judgments.
            {Tally: []uint64{2, 2, 2, 2, 2}}, // Proposal E   Equal proposals share the same rank.
            // …
        },
    })
    deliberator := &(judgment.MajorityJudgment{})
    result, err := deliberator.Deliberate(poll)

    if nil != err {
        log.Fatalf("Deliberation failed: %v", err)
    }
    
    // Proposals results are ordered like tallies, but Rank is available. 
    // result.Proposals[0].Rank == 4
    // result.Proposals[1].Rank == 1
    // result.Proposals[2].Rank == 2
    // result.Proposals[3].Rank == 3
    // result.Proposals[4].Rank == 4

    // You may also use proposals sorted by Rank ; their initial Index is available
    // result.ProposalsSorted[0].Index == 1
    // result.ProposalsSorted[1].Index == 2
    // result.ProposalsSorted[2].Index == 3
    // result.ProposalsSorted[3].Index == 0
    // result.ProposalsSorted[4].Index == 4

}

Balancing uneven proposals

Sometimes, some proposals receive more judgments than others, and the tallies are unbalanced. In those cases, a default judgment strategy has to be picked:

Static Default Grade

Missing (or "I don't know") judgments are considered of a grade defined in advance, usually to reject in order to incentivize proposals to be explicit and clear.

You may use PollTally.BalanceWithStaticDefault(defaultGrade) to that effect:

pollTally := &PollTally{
    AmountOfJudges: 10,
    Proposals: []*ProposalTally{
        {Tally: []uint64{2, 1, 2, 2, 1}}, // Proposal A
        {Tally: []uint64{3, 1, 3, 1, 1}}, // Proposal B
        {Tally: []uint64{0, 1, 1, 0, 0}}, // Proposal C
        // …
    },
}
defaultGrade := 0
pollTally.BalanceWithStaticDefault(defaultGrade)

// pollTally was mutated and now contains balanced proposals' tallies
// pollTally.Proposals[0].Tally == {4, 1, 2, 2, 1}  // Proposal A is now balanced 
// pollTally.Proposals[1].Tally == {4, 1, 3, 1, 1}  // Proposal B is now balanced
// pollTally.Proposals[2].Tally == {8, 1, 1, 0, 0}  // Proposal C is now balanced

Median Default Grade

Same behavior as static, but the default grade for each proposal is its median grade.

Use PollTally.BalanceWithMedianDefault().

Normalization

Not implemented yet ; would require either math/big for LCM or floating-point arithmetic This is part of why deciding on int types is so tricky.

License

MIT 🐜

Contribute

This project needs a review by Go devs. Feel free to suggest changes, report issues, make improvements, etc.

Some more information is available in docs/.