-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathslaveDriverDaemon.py
204 lines (188 loc) · 8.77 KB
/
slaveDriverDaemon.py
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
# -*- coding: utf-8 -*-
from __future__ import print_function
import sys
import codecs
import pandas
import csv
from fuzzywuzzy import process
import argparse
import json
import numpy as np
import math
def allocateChores(history, chores, slaves):
# the Group column of chores can be All
assert(u"U" not in slaves.columns); # to do: accommodate bias from previous weeks
# U as in potential energy
slaves[u"U"] = 1;
castes = slaves[u"Group"].unique();
castePotentials = {}; # potential energy remaining in each caste
everyoneChores = float(np.sum(chores[u"Group"] == u"All"));
assert(u"Group" in chores.columns);
assert(u"Chore" in chores.columns);
assert(u"Chores" not in slaves); # to do: handle situation where some tasks have been pre-allocated
slaves[u"Chores"] = "; "; # make it easy to strip later
# separate function for getting the random numbers?
try:
rawFile = codecs.open("lottery/items.json", encoding='utf-8', mode='r');
except IOError:
print("Failed to open {0}".format(masterFilename));
randomBlock = np.random.random_sample(size=len(chores));
randomBlock *= 40; # typical maximum lottery number
else:
# Lotto results have been scraped to prove that this program wasn't
# run repeatedly until a beneficial result was generated (presumably for the master)
lottoResults = json.load(rawFile,encoding='utf-8');
randomBlock = [];
for line in lottoResults:
randomBlock.append(int(line["winningNum"][0]));
rawFile.close;
b = 0; # place in block of random numbers
slaves.set_index(u"Email", inplace=True); # allow this to be used as a key
lastChore = 0; # when iterating, start from last pos to reduce bias of slave order
for caste in castes:
assert(caste != u"All"); # reserved word
castePopulation = float(np.sum(slaves[u"Group"] == caste));
casteChores = float(len(chores.loc[chores[u"Group"] == caste]));
casteEffort = int(math.ceil(casteChores / castePopulation + everyoneChores / float(len(slaves))));
slaves.loc[slaves[u"Group"] == caste, u"U"] = casteEffort;
print("Members of {0} caste shall exert average of {1} tasks: ({2}/{3}) + ({4}/{5})".format(
caste, casteEffort, casteChores, castePopulation, everyoneChores, float(len(slaves))));
castePotentials[caste] = castePopulation * casteEffort;
# now assign chores for that caste. These slaves will still be eligible for everyoneChores later
casteKeys = slaves.index[slaves[u"Group"] == caste]; # used for access
for c in chores.index[chores[u"Group"] == caste]:
straw = ((randomBlock[b] + lastChore) % castePotentials[caste]) + 1;
lastChore = straw; # inadvertently incremented as the slave's potential is reduced
# iterate over caste members until the straw position
cumulation = 0; # Python doesn't allow "Σ = 0;"
for slaveKey in casteKeys:
cumulation += slaves.ix[slaveKey, u"U"];
if cumulation >= straw:
try:
slaves.ix[slaveKey, u"U"] -= 1;
except ValueError:
print("There was a problem using the key to access user {0}".format(u));
return None;
slaves.ix[slaveKey, u"Chores"] += chores.ix[c,u"Chore"] + "; ";
break;
b += 1; # position in random block
assert(len(randomBlock) > b); # sufficient quantity of random numbers
castePotentials[caste] -= 1;
# now assign everyone tasks
allPotential = sum(castePotentials.values());
slaveKeys = slaves.index;
for c in chores.index[chores[u"Group"] == u"All"]:
straw = ((randomBlock[b] + lastChore) % int(allPotential)) + 1;
lastChore = straw;
# iterate over caste members until the straw position
cumulation = 0;
for slaveKey in slaveKeys:
cumulation += slaves.ix[slaveKey, u"U"];
if cumulation >= straw:
slaves.ix[slaveKey, u"U"] -= 1;
slaves.ix[slaveKey, u"Chores"] += chores.ix[c,u"Chore"] + "; ";
break;
b += 1;
assert(len(randomBlock) > b); # sufficient quantity of random numbers
allPotential -= 1;
randomBlock = randomBlock[0:b]; # this is to avoid printing unused random numbers
print("Slaves could've been pushed for another {0} tasks for more fairness.".format(allPotential));
# return to state
slaves.reset_index(inplace=True);
slaves.drop(u"U", inplace=True, axis=1);
return slaves, randomBlock;
def sendChores(chores, master, randomBlock):
# save to log file for this version of the program
try:
outFile = codecs.open("newChores.txt", encoding='utf-8', mode='w');
except IOError:
print("Failed to open {0} for writing".format("newChores.txt"));
else:
for c in chores.index:
ownershipName = chores.ix[c,u"Name"];
if(ownershipName[-1] == u"s"):
ownershipName += "'";
else:
ownershipName += "'s";
slaveChores = chores.ix[c,u"Chores"];
slaveChores = slaveChores[2:-2]; # leading and trailing ;
print("{0} chores for the week: {1}".format(ownershipName, slaveChores), file=outFile);
print("The random values used were:", file=outFile);
print(list(randomBlock), file=outFile);
#outFile.write(";".join(list(randomBlock)));
#outFile.write("\n");
outFile.close();
# to do: send the email
""" Read a csv that's at least similar to expected """
def fuzzyRead(csvFilename, expectedHeaders):
try:
rawFile = codecs.open(csvFilename, encoding='utf-8', mode='r');
except IOError:
print("Failed to open {0}".format(csvFilename));
return None
else:
hasHeaders = csv.Sniffer().has_header(rawFile.readline());
assert(hasHeaders == True);
rawFile.seek(0);
df = pandas.read_csv(rawFile);
rawFile.close();
headers = list(df.columns);
assert(headers is not None and len(headers) >= len(expectedHeaders));
for eh in expectedHeaders:
fuzzyH, scoreH = process.extractOne(eh, headers);
assert(scoreH >= 70);
assert(np.sum(headers == eh) <= 1); # unique
hCol = headers.index(fuzzyH);
headers[hCol] = eh;
df.columns = headers;
return df;
""" Get the master from a JSON file """
def gatherMaster(masterFilename):
try:
rawFile = codecs.open(masterFilename, encoding='utf-8', mode='r');
except IOError:
print("Failed to open {0}".format(masterFilename));
return None;
else:
masterSettings = json.load(rawFile,encoding='utf-8');
rawFile.close;
assert(type(masterSettings) == dict);
assert(u"master password" in masterSettings);
assert(u"randomApiKey" in masterSettings);
assert(u"master email" in masterSettings);
return masterSettings;
""" Connect to Google and pull all contacts from a group """
def pullSlaves(master, groupName):
print("To do");
if __name__ == '__main__':
parser = argparse.ArgumentParser(description="I shall allocate tasks to slaves "\
" reasonably fairly.");
parser.add_argument("-past", help="CSV file that records the history "\
"of allocation.", default=None, required=False, type=str);
parser.add_argument("-slaves", help="CSV file that records names, groups and email addresses"\
" of the slaves.", type=str, required=True);
parser.add_argument("-master", help="JSON file with settings for the master. This is used as "\
"the sender's email address etc.", type=str, required=False);
parser.add_argument("-chores", help="CSV file with list of chores and group "\
"applicability", type=str, required=True);
parser.add_argument("-v","--verbose", help="Print more comments", action='store_true',
required=False);
args = parser.parse_args();
if(args.past is not None):
history = fuzzyRead(args.past, [u"Date", u"Email", u"Chore", u"Group"]);
else:
history = None;
slaves = fuzzyRead(args.slaves, [u"Name", u"Email", u"Group"]);
if(args.verbose):
print("slaves",slaves);
choreList = fuzzyRead(args.chores, [u"Chore", u"Group"]); # to do: get chores from eg a to-do list on Google Docs
if(args.verbose):
print("chores",choreList);
newChores, randomBlock = allocateChores(history, choreList, slaves);
if(args.master is not None):
master = gatherMaster(args.master);
if(args.verbose):
print("master settings",master);
else:
master = None;
sendChores(newChores, master, randomBlock);