Overview
The Ballot class is a domain entity that represents a cast ballot in the Consensus e-voting platform. It is designed with immutability and privacy in mind, containing no voter-identifying information to ensure ballot secrecy.
Constructor
new Ballot(
ballotID: string,
electionID: string,
preferences: string[],
castAt?: Date
)
Unique identifier for the ballot
ID of the election this ballot belongs to
Array of candidate IDs representing voter preferences.
- For FPTP (First Past The Post): Single candidate ID
- For preferential voting (STV, AV): Ordered array of candidate IDs (first = most preferred)
Timestamp when the ballot was cast. Defaults to current date if not specified.
Examples
FPTP Ballot (single preference):
const fptp = new Ballot(
"fptp-ballot",
"election-1",
["candidate-A"]
);
Preferential Ballot (multiple preferences):
const preferential = new Ballot(
"pref-ballot",
"election-2",
["candidate-A", "candidate-B", "candidate-C"]
);
Properties
All properties are readonly and publicly accessible, ensuring ballot immutability after creation.
Unique identifier for the ballot (readonly)
ID of the election this ballot belongs to (readonly)
Array of candidate IDs in preference order (readonly)
- Index 0 = first preference (most preferred)
- Index 1 = second preference
- And so on…
Timestamp when the ballot was cast (readonly)
Methods
isFor()
Checks if the ballot includes a vote for a specific candidate.
isFor(candidateID: string): boolean
The candidate ID to check
Returns: true if the candidate ID appears anywhere in the preferences array.
Example:
const ballot = new Ballot(
"b3",
"e1",
["candidate-A", "candidate-B", "candidate-C"]
);
ballot.isFor("candidate-A"); // true
ballot.isFor("candidate-B"); // true
ballot.isFor("candidate-C"); // true
ballot.isFor("candidate-D"); // false
getPreferenceRank()
Returns the preference rank for a specific candidate.
getPreferenceRank(candidateID: string): number | null
The candidate ID to get the rank for
Returns:
- 1-based rank (1 = first preference, 2 = second preference, etc.)
null if the candidate is not in the preferences
Example:
const ballot = new Ballot(
"rank-ballot",
"e1",
["candidate-A", "candidate-B", "candidate-C"]
);
ballot.getPreferenceRank("candidate-A"); // 1
ballot.getPreferenceRank("candidate-B"); // 2
ballot.getPreferenceRank("candidate-C"); // 3
ballot.getPreferenceRank("candidate-Z"); // null
Privacy Compliance
The Ballot entity is designed to protect voter privacy:
- No voter identification: Does not contain
voterID or any voter-identifying information
- Immutable: All properties are readonly to prevent modification after creation
- Unlinkable: Ballot ID is independent of voter ID
Privacy test from source:
const ballot = new Ballot("ballot-privacy", "election-1", ["candidate-1"]);
// These properties exist
expect(ballot).toHaveProperty("ballotID");
expect(ballot).toHaveProperty("electionID");
expect(ballot).toHaveProperty("preferences");
// These properties do NOT exist (privacy protection)
expect(ballot).not.toHaveProperty("voterID");
expect(ballot).not.toHaveProperty("voter");
Usage Examples
FPTP Election
import { Ballot } from "./domain/entities/Ballot";
// Voter selects single candidate
const ballot = new Ballot(
"ballot-123",
"election-456",
["candidate-1"]
);
console.log(ballot.preferences.length); // 1
console.log(ballot.isFor("candidate-1")); // true
Preferential Voting Election
import { Ballot } from "./domain/entities/Ballot";
// Voter ranks multiple candidates
const ballot = new Ballot(
"ballot-789",
"election-456",
["candidate-A", "candidate-B", "candidate-C"]
);
console.log(ballot.preferences.length); // 3
console.log(ballot.getPreferenceRank("candidate-A")); // 1 (first choice)
console.log(ballot.getPreferenceRank("candidate-B")); // 2 (second choice)
Empty Ballot
// Edge case: blank ballot
const emptyBallot = new Ballot("empty-ballot", "e1", []);
console.log(emptyBallot.preferences.length); // 0
console.log(emptyBallot.isFor("candidate-A")); // false
console.log(emptyBallot.getPreferenceRank("candidate-A")); // null