Skip to content

Commit

Permalink
Added new election BudgetElection and some minor changes in `MultiC…
Browse files Browse the repository at this point in the history
…hoiceElection`
  • Loading branch information
marcvelmer committed Jan 17, 2024
1 parent e741d20 commit 138f5cd
Show file tree
Hide file tree
Showing 7 changed files with 342 additions and 25 deletions.
24 changes: 2 additions & 22 deletions src/core/election.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,8 +108,8 @@ export abstract class ElectionCore extends TransactionCore {
censusRoot: Uint8Array.from(Buffer.from(election.census.censusId, 'hex')),
censusURI: election.census.censusURI,
status: ProcessStatus.READY,
envelopeType: this.generateEnvelopeType(election),
mode: this.generateMode(election),
envelopeType: election.generateEnvelopeType(),
mode: election.generateMode(),
voteOptions: election.generateVoteOptions(),
censusOrigin: this.censusOriginFromCensusType(election.census.type),
metadata: cid,
Expand All @@ -120,26 +120,6 @@ export abstract class ElectionCore extends TransactionCore {
};
}

private static generateEnvelopeType(election: UnpublishedElection): object {
const serial = false; // TODO
const anonymous = election.electionType.anonymous;
const encryptedVotes = election.electionType.secretUntilTheEnd;
const uniqueValues = election.voteType.uniqueChoices;
const costFromWeight = election.voteType.costFromWeight;

return { serial, anonymous, encryptedVotes, uniqueValues, costFromWeight };
}

private static generateMode(election: UnpublishedElection): object {
const autoStart = election.electionType.autoStart;
const interruptible = election.electionType.interruptible;
const dynamicCensus = election.electionType.dynamicCensus;
const encryptedMetaData = false; // TODO
const preRegister = false; // TODO

return { autoStart, interruptible, dynamicCensus, encryptedMetaData, preRegister };
}

private static processStatusFromElectionStatus(status: AllElectionStatus): ProcessStatus {
if (status == ElectionStatus.UPCOMING || status == ElectionStatus.ONGOING) {
return ProcessStatus.READY;
Expand Down
134 changes: 134 additions & 0 deletions src/types/election/budget.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
import { MultiLanguage } from '../../util/lang';
import { IElectionParameters } from './election';
import { UnpublishedElection } from './unpublished';
import { ElectionMetadata, ElectionMetadataTemplate, ElectionResultsTypeNames } from '../metadata';

export interface IBudgetElectionParametersInfo extends IElectionParameters {
minStep?: number;
forceFullBudget?: boolean;
}

export interface IBudgetElectionParametersWithCensusWeight extends IBudgetElectionParametersInfo {
useCensusWeightAsBudget: true;
}

export interface IBudgetElectionParametersWithBudget extends IBudgetElectionParametersInfo {
useCensusWeightAsBudget: false;
maxBudget: number;
}

export type IBudgetElectionParameters = IBudgetElectionParametersWithCensusWeight | IBudgetElectionParametersWithBudget;

/**
* Represents a budget election
*/
export class BudgetElection extends UnpublishedElection {
private _minStep: number;
private _forceFullBudget: boolean;

/**
* Constructs a budget election
*
* @param params Budget election parameters
*/
public constructor(params: IBudgetElectionParameters) {
super(params);
this.minStep = params.minStep ?? 1;
this.forceFullBudget = params.forceFullBudget ?? false;
this.useCensusWeightAsBudget = params.useCensusWeightAsBudget;
if ('maxBudget' in params) {
this.maxBudget = params.maxBudget;
}
}

public static from(params: IBudgetElectionParameters) {
return new BudgetElection(params);
}

public addQuestion(
title: string | MultiLanguage<string>,
description: string | MultiLanguage<string>,
choices: Array<{ title: string } | { title: MultiLanguage<string> }>
) {
if (this.questions.length > 0) {
throw new Error('This type of election can only have one question');
}

return super.addQuestion(
title,
description,
choices.map((choice, index) => ({
title: typeof choice.title === 'string' ? { default: choice.title } : choice.title,
value: index,
}))
);
}

public generateVoteOptions(): object {
const maxCount = this.questions[0].choices.length;
const maxValue = 0;
const maxVoteOverwrites = this.voteType.maxVoteOverwrites;
const maxTotalCost = this.useCensusWeightAsBudget ? 0 : this.maxBudget;
const costExponent = 1;

return { maxCount, maxValue, maxVoteOverwrites, maxTotalCost, costExponent };
}

public generateEnvelopeType(): object {
const serial = false; // TODO
const anonymous = this.electionType.anonymous;
const encryptedVotes = this.electionType.secretUntilTheEnd;
const uniqueValues = false;
const costFromWeight = this.useCensusWeightAsBudget;

return { serial, anonymous, encryptedVotes, uniqueValues, costFromWeight };
}

public generateMetadata(): ElectionMetadata {
const metadata = ElectionMetadataTemplate;

metadata.type = {
name: ElectionResultsTypeNames.BUDGET,
properties: {
useCensusWeightAsBudget: this.useCensusWeightAsBudget,
maxBudget: this.useCensusWeightAsBudget ? null : this.maxBudget,
forceFullBudget: this.forceFullBudget,
minStep: this.minStep,
},
};

return super.generateMetadata(metadata);
}

get minStep(): number {
return this._minStep;
}

set minStep(value: number) {
this._minStep = value;
}

get forceFullBudget(): boolean {
return this._forceFullBudget;
}

set forceFullBudget(value: boolean) {
this._forceFullBudget = value;
}

get useCensusWeightAsBudget(): boolean {
return this.voteType.costFromWeight;
}

set useCensusWeightAsBudget(value: boolean) {
this.voteType.costFromWeight = value;
}

get maxBudget(): number {
return this.voteType.maxTotalCost;
}

set maxBudget(value: number) {
this.voteType.maxTotalCost = value;
}
}
1 change: 1 addition & 0 deletions src/types/election/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ export * from './published';
export * from './invalid';
export * from './archived';
export * from './multichoice';
export * from './budget';
11 changes: 11 additions & 0 deletions src/types/election/multichoice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,12 +61,23 @@ export class MultiChoiceElection extends UnpublishedElection {
return { maxCount, maxValue, maxVoteOverwrites, maxTotalCost, costExponent };
}

public generateEnvelopeType(): object {
const serial = false; // TODO
const anonymous = this.electionType.anonymous;
const encryptedVotes = this.electionType.secretUntilTheEnd;
const uniqueValues = !this.canRepeatChoices;
const costFromWeight = this.voteType.costFromWeight;

return { serial, anonymous, encryptedVotes, uniqueValues, costFromWeight };
}

public generateMetadata(): ElectionMetadata {
const metadata = ElectionMetadataTemplate;

metadata.type = {
name: ElectionResultsTypeNames.MULTIPLE_CHOICE,
properties: {
canAbstain: this.canAbstain,
abstainValues: [...new Array(this.canAbstain ? (this.canRepeatChoices ? 1 : this.maxNumberOfChoices) : 0)].map(
(_v, index) => String(index + this.questions[0].choices.length)
),
Expand Down
20 changes: 20 additions & 0 deletions src/types/election/unpublished.ts
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,26 @@ export class UnpublishedElection extends Election {
return { maxCount, maxValue, maxVoteOverwrites, maxTotalCost, costExponent };
}

public generateEnvelopeType(): object {
const serial = false; // TODO
const anonymous = this.electionType.anonymous;
const encryptedVotes = this.electionType.secretUntilTheEnd;
const uniqueValues = this.voteType.uniqueChoices;
const costFromWeight = this.voteType.costFromWeight;

return { serial, anonymous, encryptedVotes, uniqueValues, costFromWeight };
}

public generateMode(): object {
const autoStart = this.electionType.autoStart;
const interruptible = this.electionType.interruptible;
const dynamicCensus = this.electionType.dynamicCensus;
const encryptedMetaData = false; // TODO
const preRegister = false; // TODO

return { autoStart, interruptible, dynamicCensus, encryptedMetaData, preRegister };
}

get title(): MultiLanguage<string> {
return super.title;
}
Expand Down
17 changes: 16 additions & 1 deletion src/types/metadata/election.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ export interface IQuestion {
export enum ElectionResultsTypeNames {
SINGLE_CHOICE_MULTIQUESTION = 'single-choice-multiquestion',
MULTIPLE_CHOICE = 'multiple-choice',
BUDGET = 'budget-based',
}

export type ElectionResultsType =
Expand All @@ -46,9 +47,19 @@ export type ElectionResultsType =
| {
name: ElectionResultsTypeNames.MULTIPLE_CHOICE;
properties: {
canAbstain: boolean;
abstainValues: Array<string>;
repeatChoice: boolean;
};
}
| {
name: ElectionResultsTypeNames.BUDGET;
properties: {
useCensusWeightAsBudget: boolean;
maxBudget: number;
minStep: number;
forceFullBudget: boolean;
};
};

const electionMetadataSchema = object()
Expand Down Expand Up @@ -83,7 +94,11 @@ const electionMetadataSchema = object()
.shape({
name: string()
.required()
.oneOf([ElectionResultsTypeNames.SINGLE_CHOICE_MULTIQUESTION, ElectionResultsTypeNames.MULTIPLE_CHOICE]),
.oneOf([
ElectionResultsTypeNames.SINGLE_CHOICE_MULTIQUESTION,
ElectionResultsTypeNames.MULTIPLE_CHOICE,
ElectionResultsTypeNames.BUDGET,
]),
properties: object().optional().nullable(),
})
.required(),
Expand Down
Loading

0 comments on commit 138f5cd

Please sign in to comment.