-
Notifications
You must be signed in to change notification settings - Fork 32
/
Copy pathdetails.js
207 lines (177 loc) · 5.57 KB
/
details.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
import prisma from "db"; // Import prisma
// --> /api/events/details
export default async (req, res) => {
// Collect event ID and secret key (if it exists) from request
const {
query: { id, secret_key },
} = req;
// Collect event information from event ID
const event = await prisma.events.findOne({
where: { id: id },
});
// Collect voter information using event ID
const voters = await prisma.voters.findMany({
where: { event_uuid: id },
});
// Check for administrator access based on passed secret_key
const isAdmin =
event.secret_key && event.secret_key === secret_key ? true : false;
// After checking for administrator access, delete secret_key from event object
delete event.secret_key;
// Collect voting stastistics
const statistics = generateStatistics(
// Number of voteable subjects
JSON.parse(event.event_data).length,
// Number of max voters
event.num_voters,
// Number of credits per voter
event.credits_per_voter,
// Array of voter preferences
voters
);
// If private_key enables administrator access
if (isAdmin) {
// Pass individual voter row details to endpoint
event.voters = voters;
}
// Parse event_data
event.event_data = JSON.parse(event.event_data);
// Generate chart data for chartJS
const chart = generateChart(
event.event_data,
statistics.linear,
statistics.qv
);
// Return event data, computed statistics, and chart
res.send({
event,
statistics,
chart,
});
};
/**
* Generate QV Statistics and weights
* @param {number} subjects number of subjects
* @param {number} num_voters number of max voters
* @param {number} credits_per_voter number of credits per voter
* @param {voter[]} voters array of voter preferences
* @return {object} containing QV statistics and calculated weights
*/
function generateStatistics(subjects, num_voters, credits_per_voter, voters) {
let numberVoters = 0, // Placeholder for number of participating voters
numberVotes = 0, // Placeholder for number of placed votes
qvRaw = new Array(subjects).fill([]); // Empty raw array to hold individual votes
// For each voter
for (const voter of voters) {
// Collect voter preferences
const voter_data = voter.vote_data;
// Sum voter preferences to check if user has placed at least 1 vote
const sumVotes = voter_data
.map((subject) => Math.pow(subject.votes, 2))
.reduce((prev, curr) => prev + curr, 0);
// If user has placed a vote:
if (sumVotes > 0) {
numberVoters++; // Increment number of participating voters
numberVotes += sumVotes; // Increment number of placed votes
// For each of a users votes:
for (let i = 0; i < voter_data.length; i++) {
// Increment raw voting array for each subject
qvRaw[i] = [...qvRaw[i], voter_data[i].votes];
}
}
}
// Calculate linear weights from raw votes
const linear = calculateLinear(qvRaw);
// Calculate QV weights from raw votes
const qv = calculateQV(qvRaw);
// Return computed statistics
return {
totalVoters: voters.length,
numberVotersTotal: num_voters,
numberVoters,
numberVotesTotal: credits_per_voter * num_voters,
numberVotes,
voterParticiptation: (voters.length / numberVoters) * 100,
qvRaw,
linear,
qv,
};
}
/**
* Calculates and returnes subject weights based on linear addition
* @param {integer[][]} qvRaw
* @returns {integer[]} containing linear weights
*/
function calculateLinear(qvRaw) {
let mapped = [],
sumWeights = 0;
// For indidividual subjects in qvRaw
for (const subjectVotes of qvRaw) {
// Calculate sum of votes
const numCredits = subjectVotes.map((item, _) => Math.pow(item, 2));
const subjectSum = numCredits.reduce((a, b) => a + b, 0);
// Add sum of votes to sumWeights
sumWeights += subjectSum;
// Push linear sum to mapped
mapped.push(subjectSum);
}
let weights = []; // Final weights array
// For each sum vote in mapped
for (const sumVotes of mapped) {
// Divide by total summed # of votes to calculate weight
weights.push(sumVotes / sumWeights);
}
// Return linear weights
return weights;
}
/**
* Calculates and returns QV summed votes
* @param {integer[][]} qvRaw
* @returns {integer[]} containing QV votes
*/
function calculateQV(qvRaw) {
let votes = [];
// For individual subjects in qvRaw
for (const subjectVotes of qvRaw) {
// Push subject weights to mapped array which contains summed votes
votes.push(subjectVotes.reduce((a, b) => a + b, 0));
}
// Return votes array
return votes;
}
/**
* Returns chartJS chart data
* @param {subjects[]} subjects voteable subjects
* @param {integer[]} linearWeights linear subject weights
* @param {integer[]} weights qv subject weights
*/
function generateChart(subjects, linearWeights, weights) {
let labels = [], // Placeholder labels
linearData = [], // Placeholder series linear weight array
data = []; // Placeholder series weight array
// For each subject
for (let i = 0; i < subjects.length; i++) {
// Collect title for xaxis
labels.push(subjects[i].title);
// Collect linear weight for series
linearData.push((linearWeights[i] * 100).toFixed(2));
// Collect weight for series
data.push(weights[i]);
}
// Return data in chartJS format
return {
labels,
datasets: [
{
backgroundColor: "rgba(0, 209, 130, 1)",
label: "Quadratic Votes",
data,
},
{
backgroundColor: "rgba(15, 8, 87, 1)",
label: "% Credits",
data: linearData,
},
],
};
}