📝 README and inline docs
This commit is contained in:
parent
d533a879ef
commit
c61300c6b8
14 changed files with 139 additions and 24 deletions
|
|
@ -5,8 +5,10 @@ import (
|
|||
"strings"
|
||||
)
|
||||
|
||||
// Quorum defines a custom uint8 type for storing a quorum.
|
||||
type Quorum uint8
|
||||
|
||||
// Supported quorums are:
|
||||
const (
|
||||
Simple Quorum = iota
|
||||
OneFifth
|
||||
|
|
@ -21,6 +23,7 @@ const (
|
|||
Unanimous
|
||||
)
|
||||
|
||||
// ValidQuorums returns a []Quorum of all supported quorums.
|
||||
func ValidQuorums() []Quorum {
|
||||
return []Quorum{
|
||||
Simple, Unanimous,
|
||||
|
|
@ -31,6 +34,8 @@ func ValidQuorums() []Quorum {
|
|||
}
|
||||
}
|
||||
|
||||
// FromString takes a string and returns an initialized Quorum or an error if
|
||||
// the string could not be parsed.
|
||||
func FromString(s string) (Quorum, error) {
|
||||
for _, q := range ValidQuorums() {
|
||||
if strings.ToUpper(q.String()) == strings.ToUpper(s) {
|
||||
|
|
@ -40,6 +45,7 @@ func FromString(s string) (Quorum, error) {
|
|||
return Simple, fmt.Errorf("inalid quorum: %s", s)
|
||||
}
|
||||
|
||||
// String implements fmt.Stringer
|
||||
func (q Quorum) String() string {
|
||||
switch q {
|
||||
case Simple:
|
||||
|
|
@ -69,6 +75,8 @@ func (q Quorum) String() string {
|
|||
}
|
||||
}
|
||||
|
||||
// IsSatisfied takes the number of eligible and actual votes and returns a bool
|
||||
// indicating if the quroum is satisfied or not.
|
||||
func (q Quorum) IsSatisfied(possibleVotes, totalVotes int) bool {
|
||||
if totalVotes == 0 {
|
||||
return false
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@ package voting
|
|||
|
||||
import "code.c-base.org/baccenfutter/govote/voting/vote"
|
||||
|
||||
// Result is a data-container for the results of a voting.
|
||||
// It can easily be accessed in templates.
|
||||
type Result struct {
|
||||
Quorum bool
|
||||
Threshold bool
|
||||
|
|
|
|||
|
|
@ -5,8 +5,10 @@ import (
|
|||
"strings"
|
||||
)
|
||||
|
||||
// Threshold defines a custom uint8 type for representing a threshold.
|
||||
type Threshold uint8
|
||||
|
||||
// Supported thresholds are:
|
||||
const (
|
||||
Simple Threshold = iota
|
||||
OneFifth
|
||||
|
|
@ -21,16 +23,19 @@ const (
|
|||
Unanimous
|
||||
)
|
||||
|
||||
// ValidThresholds returns a []Threshold of all supported thresholds.
|
||||
func ValidThresholds() []Threshold {
|
||||
return []Threshold{
|
||||
Simple, Unanimous,
|
||||
OneFifth, OneQuarter, OneThird, OneHalf,
|
||||
TwoFifths, TwoThirds,
|
||||
ThreeFifths, ThreeQuarters, ThreeFifths,
|
||||
ThreeQuarters, ThreeFifths,
|
||||
FourFifths,
|
||||
}
|
||||
}
|
||||
|
||||
// FromString takes a string and returns an initialized Threshold or an error
|
||||
// if the string could not be parsed.
|
||||
func FromString(s string) (Threshold, error) {
|
||||
for _, t := range ValidThresholds() {
|
||||
if strings.ToUpper(t.String()) == strings.ToUpper(s) {
|
||||
|
|
@ -40,6 +45,7 @@ func FromString(s string) (Threshold, error) {
|
|||
return Simple, fmt.Errorf("invalid threshold: %s", s)
|
||||
}
|
||||
|
||||
// String implements fmt.Stringer
|
||||
func (t Threshold) String() string {
|
||||
switch t {
|
||||
case Simple:
|
||||
|
|
@ -69,6 +75,8 @@ func (t Threshold) String() string {
|
|||
}
|
||||
}
|
||||
|
||||
// IsSatisfied takes the number of all votes, yes votes and no votes and
|
||||
// returns a bool indicating if the threshold is satisfied.
|
||||
func (t Threshold) IsSatisfied(totalVotes, yesVotes, noVotes int) bool {
|
||||
if totalVotes == 0 {
|
||||
return false
|
||||
|
|
|
|||
|
|
@ -5,8 +5,10 @@ import (
|
|||
"strings"
|
||||
)
|
||||
|
||||
// Choice defines a custom int8 type for use as choice in a vote.
|
||||
type Choice int8
|
||||
|
||||
// String implements fmt.Stringer
|
||||
func (choice Choice) String() string {
|
||||
switch choice {
|
||||
case Yes:
|
||||
|
|
@ -20,16 +22,20 @@ func (choice Choice) String() string {
|
|||
}
|
||||
}
|
||||
|
||||
// Available choices are:
|
||||
const (
|
||||
Abstain Choice = 0
|
||||
Yes Choice = 1
|
||||
No Choice = -1
|
||||
)
|
||||
|
||||
// ValidChoices returns a []Choice with all available choices.
|
||||
func ValidChoices() []Choice {
|
||||
return []Choice{Yes, No, Abstain}
|
||||
}
|
||||
|
||||
// ChoiceFromString takes a string and returns an initialized Choice or an
|
||||
// error if the string couldn't be parsed.
|
||||
func ChoiceFromString(s string) (Choice, error) {
|
||||
for _, c := range ValidChoices() {
|
||||
if strings.ToUpper(c.String()) == strings.ToUpper(s) {
|
||||
|
|
|
|||
|
|
@ -5,13 +5,19 @@ import (
|
|||
"time"
|
||||
)
|
||||
|
||||
// Vote represents an individual vote in a voting.
|
||||
type Vote struct {
|
||||
Id string
|
||||
Elector string
|
||||
Choice Choice
|
||||
// Id contains the unique ID of the vote.
|
||||
Id string
|
||||
// Elector contains the name of whoever placed the vote.
|
||||
Elector string
|
||||
// Choice contains the choice of the vote.
|
||||
Choice Choice
|
||||
// timestamp contains the UTC timestamp of when the vote was placed.
|
||||
timestamp time.Time
|
||||
}
|
||||
|
||||
// NewVote returns an initialized Vote.
|
||||
func NewVote(id, elector string, choice Choice) Vote {
|
||||
return Vote{
|
||||
Id: id,
|
||||
|
|
@ -21,6 +27,8 @@ func NewVote(id, elector string, choice Choice) Vote {
|
|||
}
|
||||
}
|
||||
|
||||
// NewVoteWithTimestamp returns an initialized Vote with a predefined
|
||||
// timestamp. This can be useful when loading a vote from the store.
|
||||
func NewVoteWithTimestamp(id, elector string, choice Choice, timestamp time.Time) Vote {
|
||||
return Vote{
|
||||
Id: id,
|
||||
|
|
@ -30,6 +38,7 @@ func NewVoteWithTimestamp(id, elector string, choice Choice, timestamp time.Time
|
|||
}
|
||||
}
|
||||
|
||||
// String implements fmt.Stringer
|
||||
func (vote Vote) String() string {
|
||||
return fmt.Sprintf("%s %s %s %s", vote.Id, vote.timestamp.Format(time.DateTime), vote.Choice, vote.Elector)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,18 +12,28 @@ import (
|
|||
)
|
||||
|
||||
type (
|
||||
// Voting represents a voting with all its data and meta-data.
|
||||
Voting struct {
|
||||
id string
|
||||
// id is the unique ID of the vote.
|
||||
id string
|
||||
// referendum contains the subject or title of the voting
|
||||
referendum string
|
||||
deadline time.Time
|
||||
quorum quorum.Quorum
|
||||
threshold threshold.Threshold
|
||||
electors []string
|
||||
// deadline defines the point in time when the voting closes
|
||||
deadline time.Time
|
||||
// quorum defines the mininimum required eligible votes
|
||||
quorum quorum.Quorum
|
||||
// threshold defines the minimum required YES votes
|
||||
threshold threshold.Threshold
|
||||
// electors contains the list of eligible voters (empty if anyone can vote)
|
||||
electors []string
|
||||
// annonymous defines if the voting is anonymous or public
|
||||
annonymous bool
|
||||
votes []vote.Vote
|
||||
// votes holds all votes associated with this voting
|
||||
votes []vote.Vote
|
||||
}
|
||||
)
|
||||
|
||||
// NewVoting returns an initialized Voting.
|
||||
func NewVoting(id, r string, d time.Time, q quorum.Quorum, t threshold.Threshold, e []string, a bool) *Voting {
|
||||
return &Voting{
|
||||
id: id,
|
||||
|
|
@ -37,6 +47,8 @@ func NewVoting(id, r string, d time.Time, q quorum.Quorum, t threshold.Threshold
|
|||
}
|
||||
}
|
||||
|
||||
// NewVotingWithVotes returns an initialized Voting with pre-defined votes.
|
||||
// This is convenient when loading a voting from the store and populating it in one call.
|
||||
func NewVotingWithVotes(id, r string, d time.Time, q quorum.Quorum, t threshold.Threshold, e []string, a bool, v []vote.Vote) *Voting {
|
||||
voting := NewVoting(id, r, d, q, t, e, a)
|
||||
for i := range v {
|
||||
|
|
@ -45,22 +57,24 @@ func NewVotingWithVotes(id, r string, d time.Time, q quorum.Quorum, t threshold.
|
|||
return voting
|
||||
}
|
||||
|
||||
// String implements fmt.Stringer.
|
||||
func (v Voting) String() string {
|
||||
// initialize vars with all the metadata
|
||||
var (
|
||||
possibleVotes int = len(v.electors)
|
||||
totalVotes int = len(v.Votes())
|
||||
yesVotes int = len(v.yesVotes())
|
||||
noVotes int = len(v.noVotes())
|
||||
deadlineStatus string = "🎭 ONGOING 🎭"
|
||||
quorumStatus string = "❌ FAIL"
|
||||
thresholdStatus string = "❌ FAIL"
|
||||
out string = ""
|
||||
possibleVotes int = len(v.electors)
|
||||
totalVotes int = len(v.Votes())
|
||||
yesVotes int = len(v.yesVotes())
|
||||
noVotes int = len(v.noVotes())
|
||||
deadlineStatus string = "🎭 ONGOING 🎭"
|
||||
quorumStatus string = "❌ FAIL"
|
||||
thresholdStatus string = "❌ FAIL"
|
||||
out string = ""
|
||||
quorumSatisfied = v.quorum.IsSatisfied(possibleVotes, totalVotes)
|
||||
thresholdSatisfied = v.threshold.IsSatisfied(totalVotes, yesVotes, noVotes)
|
||||
votingSatisfied = quorumSatisfied && thresholdSatisfied
|
||||
)
|
||||
|
||||
quorumSatisfied := v.quorum.IsSatisfied(possibleVotes, totalVotes)
|
||||
thresholdSatisfied := v.threshold.IsSatisfied(totalVotes, yesVotes, noVotes)
|
||||
votingSatisfied := quorumSatisfied && thresholdSatisfied
|
||||
|
||||
// check the deadline
|
||||
if time.Now().UTC().After(v.deadline) {
|
||||
if votingSatisfied {
|
||||
deadlineStatus = "✅ APROVED ✅"
|
||||
|
|
@ -68,6 +82,8 @@ func (v Voting) String() string {
|
|||
deadlineStatus = "❌ REJECTED ❌"
|
||||
}
|
||||
}
|
||||
|
||||
// check quorum and threshold
|
||||
if v.quorum.IsSatisfied(possibleVotes, totalVotes) {
|
||||
quorumStatus = "✅ PASS"
|
||||
}
|
||||
|
|
@ -75,6 +91,7 @@ func (v Voting) String() string {
|
|||
thresholdStatus = "✅ PASS"
|
||||
}
|
||||
|
||||
// assemble output string
|
||||
out += fmt.Sprintf("Referendum: %s\n", strings.ToUpper(v.referendum))
|
||||
out += fmt.Sprintf("Deadline : %s UTC\n", v.deadline.Format(time.DateTime))
|
||||
out += fmt.Sprintf("Quorum : %s (%d/%d) (required: %s)\n", quorumStatus, totalVotes, possibleVotes, v.quorum)
|
||||
|
|
@ -88,6 +105,7 @@ func (v Voting) String() string {
|
|||
)
|
||||
out += fmt.Sprintf("Status : %s\n", deadlineStatus)
|
||||
|
||||
// only show votes when not anonymous
|
||||
if !v.annonymous {
|
||||
out += "\n"
|
||||
if v.votes != nil && len(v.votes) > 0 {
|
||||
|
|
@ -97,29 +115,36 @@ func (v Voting) String() string {
|
|||
}
|
||||
}
|
||||
|
||||
// return the output string
|
||||
return out
|
||||
}
|
||||
|
||||
// ID returns the unique ID of this voting.
|
||||
func (v Voting) ID() string {
|
||||
return v.id
|
||||
}
|
||||
|
||||
// Referendum returns the referendum of this voting.
|
||||
func (v Voting) Referendum() string {
|
||||
return v.referendum
|
||||
}
|
||||
|
||||
// Deadline returns the deadline of this voting.
|
||||
func (v Voting) Deadline() time.Time {
|
||||
return v.deadline
|
||||
}
|
||||
|
||||
// Quorum returns the quorum of this voting.
|
||||
func (v Voting) Quorum() string {
|
||||
return v.quorum.String()
|
||||
}
|
||||
|
||||
// Threshold returns the threshold of this voting.
|
||||
func (v Voting) Threshold() string {
|
||||
return v.threshold.String()
|
||||
}
|
||||
|
||||
// Electors returns the list of eligible voters of this voting.
|
||||
func (v Voting) Electors() []string {
|
||||
electors := make([]string, len(v.electors))
|
||||
for i := range v.electors {
|
||||
|
|
@ -128,14 +153,17 @@ func (v Voting) Electors() []string {
|
|||
return electors
|
||||
}
|
||||
|
||||
// Anonymous returns the anonymous setting of this voting.
|
||||
func (v Voting) Anonymous() bool {
|
||||
return v.annonymous
|
||||
}
|
||||
|
||||
// IsOpen returns true while the deadline has not yet been reached.
|
||||
func (v Voting) IsOpen() bool {
|
||||
return v.deadline.After(time.Now().UTC())
|
||||
}
|
||||
|
||||
// Result returns a Result based on this voting.
|
||||
func (v Voting) Result() Result {
|
||||
var (
|
||||
possibleVotes = len(v.electors)
|
||||
|
|
@ -162,22 +190,33 @@ func (v Voting) Result() Result {
|
|||
}
|
||||
}
|
||||
|
||||
// Vote takes a vote.Vote and places it.
|
||||
// Placing a vote after the deadline has passed will raise an error. Placing a
|
||||
// vote with an elector not in the list of eligible electors will raise an
|
||||
// error.
|
||||
func (v *Voting) Vote(vote vote.Vote) error {
|
||||
// check if deadline has passed
|
||||
if time.Now().UTC().After(v.deadline) {
|
||||
return fmt.Errorf("deadline has passed")
|
||||
}
|
||||
// place vote if elector is eligible
|
||||
for _, elector := range v.electors {
|
||||
if elector == vote.Elector {
|
||||
v.votes = append(v.votes, vote)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
// raise an error if not eligible
|
||||
return fmt.Errorf("not eligable to vote: %s", vote.Elector)
|
||||
}
|
||||
|
||||
// Votes returns the normalized list of effective votes.
|
||||
// Only the last vote of each elector before the deadline counts.
|
||||
func (v Voting) Votes() []vote.Vote {
|
||||
votes := []vote.Vote{}
|
||||
nextVote:
|
||||
// iterate over all placed votes in reverse order only respecting the first
|
||||
// vote of each elector (effectively the final vote of each elector).
|
||||
for i := len(v.votes) - 1; i >= 0; i-- {
|
||||
elector := v.votes[i].Elector
|
||||
for _, e := range votes {
|
||||
|
|
@ -188,25 +227,29 @@ nextVote:
|
|||
votes = append(votes, v.votes[i])
|
||||
}
|
||||
|
||||
// anonymize
|
||||
// optionally, anonymize the votes before returning them
|
||||
if v.annonymous {
|
||||
for i := range votes {
|
||||
votes[i].Elector = ""
|
||||
}
|
||||
}
|
||||
|
||||
return votes
|
||||
}
|
||||
|
||||
// yesVotes returns a []vote.Vote of all yes votes.
|
||||
func (v Voting) yesVotes() []vote.Vote {
|
||||
filterFunc := func(_v vote.Vote) bool { return _v.Choice == vote.Yes }
|
||||
return filter.Choose(v.Votes(), filterFunc).([]vote.Vote)
|
||||
}
|
||||
|
||||
// noVotes returns a []vote.Vote of all no votes.
|
||||
func (v Voting) noVotes() []vote.Vote {
|
||||
filterFunc := func(_v vote.Vote) bool { return _v.Choice == vote.No }
|
||||
return filter.Choose(v.Votes(), filterFunc).([]vote.Vote)
|
||||
}
|
||||
|
||||
// abstainVotes returns a []vote.Vote of all abstain votes.
|
||||
func (v Voting) abstainVotes() []vote.Vote {
|
||||
filterFunc := func(_v vote.Vote) bool { return _v.Choice == vote.Abstain }
|
||||
return filter.Choose(v.Votes(), filterFunc).([]vote.Vote)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue