Add stats

trunk
mio 2019-01-06 22:30:59 +00:00
parent 79a4d3411b
commit 93a0301b6e
1 changed files with 159 additions and 32 deletions

183
wilty.py
View File

@ -11,28 +11,50 @@ class Wilty():
self.save_dir = ".botany" self.save_dir = ".botany"
self.plant_file = "_plant_data.json" self.plant_file = "_plant_data.json"
self.visit_file = "visitors.json" self.visit_file = "visitors.json"
self.harvest_file = "harvest_file.json"
self.plants = {} self.plants = {}
self.loadData() self.stats_model = {
"total": 0,
"live": 0,
"seed": 0,
"young": 0,
"mature": 0,
"succ": 0,
"tree": 0,
"nonflower": 0,
"flower": 0,
"gen": 0,
"avg_gen": 0,
"harv": 0,
"avg_harv": 0,
}
self.border = "-" * 47
def convertTS(self, ts): def formatTime(self, ts):
"""Convert a timestamp in seconds to a legible format, e.g. 12d 12h """Format an integer timestamp (seconds) to a timestamp string (days,
12m.""" hours and minutes), e.g. 12d 12h 12m.
"""
days = int(ts / 86400) days = int(ts / 86400)
hours = int((ts % 86400) / 3600) hours = int((ts % 86400) / 3600)
mins = round(((ts % 86400) % 3600) / 60) mins = round(((ts % 86400) % 3600) / 60)
return str(days) + "d " + str(hours) + "h " + str(mins) + "m" return str(days) + "d " + str(hours) + "h " + str(mins) + "m"
def getUsers(self): def getUsers(self):
"""Load usernames into the plants dictionary as keys.""" """Load usernames into the plants dictionary as keys. Only users whose
plant data is peer-readable will be added.
"""
sys_users = pwd.getpwall() sys_users = pwd.getpwall()
for u in sys_users: for u in sys_users:
if os.path.exists("/home/" + u.pw_name + "/" + self.save_dir): sv_dir = "/home/" + u.pw_name + "/" + self.save_dir + "/"
if os.path.exists(sv_dir) and \
os.access(sv_dir + u.pw_name + self.plant_file, os.R_OK):
self.plants[u.pw_name] = {} self.plants[u.pw_name] = {}
def checkPlant(self, user): def checkPlant(self, user):
"""Given the username, calculates the last time since the user's """Given the username, calculates the last time since the user's
plant was watered, estimates whether it is dead and updates the plants plant was watered, estimates whether it is dead and updates the plants
dictionary.""" dictionary.
"""
# Get last visitor timestamp and check if later than user's # Get last visitor timestamp and check if later than user's
visitor_ts = 0 visitor_ts = 0
if self.plants[user]["visitors"] != []: if self.plants[user]["visitors"] != []:
@ -40,24 +62,20 @@ class Wilty():
watered = max(self.plants[user]["last_watered"], visitor_ts) watered = max(self.plants[user]["last_watered"], visitor_ts)
time_since = int(time.time()) - watered time_since = int(time.time()) - watered
self.plants[user]["time_since"] = time_since self.plants[user]["time_since"] = time_since
# >432000 (5 days) = dead plant # >5 days = dead plant
if time_since > 432000: if time_since > 432000:
self.plants[user]["is_dead"] = True self.plants[user]["is_dead"] = True
self.plants[user]["time_since_fmt"] = self.convertTS(time_since) self.plants[user]["time_since_fmt"] = self.formatTime(time_since)
def loadData(self): def getRawData(self):
"""Load plant and visitor data into plants dictionary.""" """Load plant data into the plants dictionary."""
self.getUsers() self.getUsers()
for u in self.plants: for u in self.plants:
sv_dir = "/home/" + u + "/" + self.save_dir + "/" sv_dir = "/home/" + u + "/" + self.save_dir + "/"
# Get plant data # Get plant data
if os.access(sv_dir + u + self.plant_file, os.R_OK):
with open(sv_dir + u + self.plant_file) as plant_fh: with open(sv_dir + u + self.plant_file) as plant_fh:
plant_json = plant_fh.read() plant_json = plant_fh.read()
self.plants[u] = json.loads(plant_json) self.plants[u] = json.loads(plant_json)
self.plants[u]["allow_query"] = True
else:
self.plants[u]["allow_query"] = False
# Get visitor data # Get visitor data
if os.access(sv_dir + self.visit_file, os.R_OK) and \ if os.access(sv_dir + self.visit_file, os.R_OK) and \
os.access(sv_dir + self.visit_file, os.W_OK): os.access(sv_dir + self.visit_file, os.W_OK):
@ -68,37 +86,146 @@ class Wilty():
else: else:
self.plants[u]["allow_visit"] = False self.plants[u]["allow_visit"] = False
self.plants[u]["visitors"] = [] self.plants[u]["visitors"] = []
# Update plant watered state if plant data is readable # Get harvest data
if self.plants[u]["allow_query"]: if os.access(sv_dir + self.harvest_file, os.R_OK):
with open(sv_dir + self.harvest_file) as harvest_fh:
harvest_json = harvest_fh.read()
self.plants[u]["harvests"] = json.loads(harvest_json)
else:
self.plants[u]["harvests"] = {}
# Update plant watered state
self.checkPlant(u) self.checkPlant(u)
def listLivePlants(self, *args, **kwargs): def listLivePlants(self, *args, **kwargs):
"""List living plants open to visitors.""" """Prints a list of living plants open to visitors. Optionally specify
a sort flag to sort the list alphabetically by username or plant type,
e.g. sort=\"plant\". By default, the list is sorted by the longest
interval since the last watered time descending.
"""
sort_l = [] sort_l = []
for p in self.plants: for p in self.plants:
if self.plants[p]["allow_query"] and \ if self.plants[p]["allow_visit"] and not self.plants[p]["is_dead"]:
self.plants[p]["allow_visit"] and \
not self.plants[p]["is_dead"]:
sort_l.append((p, self.plants[p]["description"].split()[-1], \ sort_l.append((p, self.plants[p]["description"].split()[-1], \
self.plants[p]["time_since_fmt"], \ self.plants[p]["time_since_fmt"], \
self.plants[p]["time_since"])) self.plants[p]["time_since"]))
# Sort by column # Sort by column
column = kwargs.get("sort", "") if kwargs.get("sort", "") == "user":
if column == "user":
sort_l.sort(key=lambda c: c[0]) sort_l.sort(key=lambda c: c[0])
elif column == "plant": elif kwargs.get("sort", "") == "plant":
sort_l.sort(key=lambda c: c[1]) sort_l.sort(key=lambda c: c[1])
else: else:
sort_l.sort(key=lambda c: c[3], reverse=True) sort_l.sort(key=lambda c: c[3], reverse=True)
# Format output # Format output
border = "-" * 47 print(self.border)
print(border)
print("{:<20s}{:<15s}{:<15s}".format("User", "Plant", "Last Watered")) print("{:<20s}{:<15s}{:<15s}".format("User", "Plant", "Last Watered"))
print(border) print(self.border)
for i in sort_l: for i in sort_l:
print("{:<20s}{:<15s}{:<15s}".format(i[0], i[1], i[2])) print("{:<20s}{:<15s}{:<15s}".format(i[0], i[1], i[2]))
print(border) print(self.border)
def countPlantStages(self, key, count):
"""Count the plants in different stages (seedling, young, mature). Take
as arguments a key for plants dictionary lookup and a dictionary to
append the results."""
if "stage" in self.plants[key]:
if self.plants[key]["stage"] == ("mature" or "flowering" or \
"seed-bearing"):
count["mature"] += 1
elif self.plants[key]["stage"] == "young":
count["young"] += 1
else:
count["seed"] += 1
else:
count["seed"] += 1
def countPlantGroups(self, key, count):
"""Count plants in type groups."""
succulents = ["agave", "aloe", "cactus", "lithops", "pachypodium"]
trees = ["baobab", "ficus", "palm"]
noflowers = ["fern", "moss"]
plant_type = self.plants[key]["description"].split()[-1]
if plant_type in succulents:
count["succ"] += 1
elif plant_type in trees:
count["tree"] += 1
elif plant_type in noflowers:
count["nonflower"] += 1
elif plant_type != ("seed" or "seedling"):
count["flower"] += 1
def countGenRate(self, key, count):
"""Count total generation rates."""
if "generation" in self.plants[key]:
count["gen"] += self.plants[key]["generation"]
else:
count["gen"] += 1
def avgGenRate(self, count):
"""Calculate the average plant generation."""
count["avg_gen"] = round(count["gen"] / count["total"], \
3)
def countHarvests(self, key, count):
"""Count the total number of harvests."""
if self.plants[key]["harvests"] != {}:
count["harv"] += len(self.plants[key]["harvests"])
else:
count["harv"] += 0
def avgHarvests(self, count):
"""Calculate the average number of harvests per user."""
count["avg_harv"] = round(count["harv"] / count["total"], 3)
def genStats(self, subset):
"""Generate garden stats of all plants or only live plants."""
stats = dict(self.stats_model)
if subset == "live":
for p in self.plants:
if not self.plants[p]["is_dead"]:
stats["live"] += 1
self.countPlantStages(p, stats)
self.countPlantGroups(p, stats)
self.countGenRate(p, stats)
self.countHarvests(p, stats)
stats["total"] = stats["live"]
else:
for p in self.plants:
self.countPlantStages(p, stats)
self.countPlantGroups(p, stats)
self.countGenRate(p, stats)
self.countHarvests(p, stats)
stats["total"] = len(self.plants)
self.avgGenRate(stats)
self.avgHarvests(stats)
return stats
def listStats(self, *args, **kwargs):
"""Print plant stats. Optionally use a subset flag to get stats on live
plants only, e.g. subset=\"live\". By default, show stats based on the
total number of plants.
"""
if kwargs.get("subset", "all") == "live":
stats = self.genStats("live")
else:
stats = self.genStats("all")
print("Garden Stats (" + kwargs.get("subset", "all") + " plants)")
print(self.border)
print(
"Avg. generation: " + str(stats["avg_gen"]) +
"\nAvg. harvests per user: " + str(stats["avg_harv"]) +
"\nSeedlings: " + str(stats["seed"]) +
"\nYoung plants: " + str(stats["young"]) +
"\nMature plants: " + str(stats["mature"]) +
"\nFlowering shrubs: " + str(stats["flower"]) +
"\nNon-flowering plants: " + str(stats["nonflower"]) +
"\nSucculents: " + str(stats["succ"]) +
"\nTrees: " + str(stats["tree"]) +
"\nTotal plants: " + str(stats["total"])
)
print(self.border)
instance = Wilty() instance = Wilty()
instance.getRawData()
instance.listLivePlants() instance.listLivePlants()
instance.listStats()