From 739e107240a6594839fbd349b972694527589434 Mon Sep 17 00:00:00 2001 From: Christian Heller <c.heller@plomlompom.de> Date: Mon, 26 Dec 2022 17:06:33 +0100 Subject: [PATCH 1/1] Add income tracker. --- income_progress_bars.py | 260 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 260 insertions(+) create mode 100644 income_progress_bars.py diff --git a/income_progress_bars.py b/income_progress_bars.py new file mode 100644 index 0000000..48b1179 --- /dev/null +++ b/income_progress_bars.py @@ -0,0 +1,260 @@ +# Python 3 server example +from http.server import BaseHTTPRequestHandler, HTTPServer +import os +import json + +hostName = "localhost" +serverPort = 8080 + +header = """<html> +<meta charset="UTF-8"> +<style> +body { + font-family: monospace; + background-color: #e0e0ff; +} +.countable { + font-family: monospace; + text-align: right; +} +td, th { + border: 1px solid black; +} +.progressbar { + width: 100px; + height: 20px; + background-color: black; + border: none; +} +.time_progress { + position: absolute; + height: 20px; + background-color: white; + width: 2px; + border-left: 1px solid black; + border-right: 1px solid black; + z-index: 2; +} +.progress { + height: 20px; + z-index: 1; +} +.surplus { + background-color: green; +} +input { + font-family: monospace; + text-align: right; +} +input.rate { + text-align: center; + width: 4em; +} +input.minutes { + width: 4em; +} +input.workdays { + width: 3em; +} +input.year_goal { + width: 5em; +} +.diff_goal { + position: absolute; + width: 7em; + text-align: right; + color: white; + z-index: 3; +} +table { + margin-bottom: 2em; +} +.input_container { + text-align: center; +} +</style> +<body> +<table> +<tr><th /><th>earned</th><th>progress</th><th>surplus</th></tr > +""" +footer = """</table> +<form action="/" method="POST"> +<table> +<tr><th>hourly rate</th><th>worked today</th></tr> +<tr><td class="input_container"><input type="number" min="1" class="rate" name="workday_hourly_rate_1" value="%s"/>â¬</td><td><input type="number" min="0" class="minutes" name="workday_minutes_worked_1" value="%s" step="5" /> minutes</td> +<tr><td class="input_container"><input type="number" min="1" class="rate" name="workday_hourly_rate_2" value="%s"/>â¬</td><td><input type="number" min="0" class="minutes" name="workday_minutes_worked_2" value="%s" step="5" /> minutes</td> +<tr><td class="input_container"><input type="number" min="1" class="rate" name="workday_hourly_rate_3" value="%s"/>â¬</td><td><input type="number" min="0" class="minutes" name="workday_minutes_worked_3" value="%s" step="5" /> minutes</td> +<table> +<tr><th>yearly income goal</th><td><input type="number" class="year_goal" min="1" name="year_goal" value="%s" />â¬</td></tr> +<tr><th>monthly income goal</th><td class="countable">%.2fâ¬</td></tr> +<tr><th>weekly income goal</th><td class="countable">%.2fâ¬</td></tr> +<tr><th>workdays per month</th><td class="input_container"><input type="number" class="workdays" min="1" max="28" name="workdays_per_month" value="%s" /></td></tr> +<tr><th>workday income goal</th><td class="countable">%.2fâ¬</td></tr> +<tr><th>workdays per week</th><td class="countable">%.2fâ¬</td></tr> +</table> +<input type="submit" name="update" value="update inputs" /> +<input type="submit" name="finish" value="finish day" /> +</form> +</body +</html>""" + +db_default = { + "timestamp_year": 0, + "timestamp_month": 0, + "timestamp_week": 0, + "year_income": 0, + "month_income": 0, + "week_income": 0, + "workday_hourly_rate_1": 10, + "workday_minutes_worked_1": 0, + "workday_hourly_rate_2": 25, + "workday_minutes_worked_2": 0, + "workday_hourly_rate_3": 50, + "workday_minutes_worked_3": 0, + "year_goal": 20000, + "workdays_per_month": 16 +} +db_file = "db.json" +lock_file = "db.lock" +def load_db(): + if os.path.exists(db_file): + with open(db_file, "r") as f: + return json.load(f) + else: + return db_default + +class MyServer(BaseHTTPRequestHandler): + + def do_POST(self): + from urllib.parse import parse_qs + length = int(self.headers['content-length']) + postvars = parse_qs(self.rfile.read(length), keep_blank_values=1) + db = load_db() + db["workday_minutes_worked_1"] = int(postvars[b'workday_minutes_worked_1'][0].decode()) + db["workday_minutes_worked_2"] = int(postvars[b'workday_minutes_worked_2'][0].decode()) + db["workday_minutes_worked_3"] = int(postvars[b'workday_minutes_worked_3'][0].decode()) + db["workday_hourly_rate_1"] = int(postvars[b'workday_hourly_rate_1'][0].decode()) + db["workday_hourly_rate_2"] = int(postvars[b'workday_hourly_rate_2'][0].decode()) + db["workday_hourly_rate_3"] = int(postvars[b'workday_hourly_rate_3'][0].decode()) + db["year_goal"] = int(postvars[b'year_goal'][0].decode()) + db["workdays_per_month"] = int(postvars[b'workdays_per_month'][0].decode()) + if b'finish' in postvars.keys(): + day_income = (db["workday_minutes_worked_1"] / 60.0) * db["workday_hourly_rate_1"] + day_income += (db["workday_minutes_worked_2"] / 60.0) * db["workday_hourly_rate_2"] + day_income += (db["workday_minutes_worked_3"] / 60.0) * db["workday_hourly_rate_3"] + db["year_income"] += day_income + db["month_income"] += day_income + db["week_income"] += day_income + db["workday_minutes_worked_1"] = 0 + db["workday_minutes_worked_2"] = 0 + db["workday_minutes_worked_3"] = 0 + if not os.path.exists(lock_file): + with open(lock_file, "w+"): pass + with open(db_file, "w") as f: + json.dump(db, f) + os.remove(lock_file) + self.send_response(302) + self.send_header('Location', '/') + self.end_headers() + else: + self.send_response(400) + self.end_headers() + self.wfile.write(bytes("Sorry, lock file!", "utf-8")) + + def do_GET(self): + import datetime + import calendar + self.send_response(200) + self.send_header("Content-type", "text/html") + self.end_headers() + db = load_db() + today = datetime.datetime.now() + if not os.path.exists(lock_file): + update_db = False + if today.year != db["timestamp_year"]: + db["timestamp_year"] = today.year + db["year_income"] = 0 + update_db = True + if today.month != db["timestamp_month"]: + db["timestamp_month"] = today.month + db["month_income"] = 0 + update_db = True + if today.isocalendar()[1] != db["timestamp_week"]: + db["timestamp_week"] = today.isocalendar()[1] + db["week_income"] = 0 + update_db = True + if update_db: + print("Resetting timestamp") + with open(lock_file, "w+"): pass + with open(db_file, "w") as f: + json.dump(db, f) + os.remove(lock_file) + else: + self.send_response(400) + self.end_headers() + self.wfile.write(bytes("Sorry, lock file!", "utf-8")) + return + day_of_year = today.toordinal() - datetime.date(today.year, 1, 1).toordinal() + 1 + year_length = 365 + calendar.isleap(today.year) + workday_goal = db["year_goal"] / 12 / db["workdays_per_month"] + workdays_per_week = (db["workdays_per_month"] * 12) / (year_length / 7) + month_goal = db["year_goal"] / 12 + week_goal = db["year_goal"] / (year_length / 7) + def success_color(success): + if success < 0.5: + return "red"; + elif success < 1: + return "yellow"; + else: + return "green" + def progressbar(title, earned, goal, time_progress=-1): + time_progress_indicator = "" + success_income = earned / goal + success_income_cut = min(success_income, 1.0) + success_income_bonus = max(success_income - 1.0, 0) + success = success_income + 0 + diff_goal = earned - goal + if time_progress >= 0: + success = success_income / time_progress + time_progress_indicator = "<div class=\"time_progress\" style=\"margin-left: %spx\"></div>" % int(time_progress * 100) + return "<tr><th>%s</th>" \ + "<td class=\"countable\">%.2fâ¬</td>" \ + "<td class=\"progressbar\">%s<div class=\"progress\" style=\"background-color: %s; width: %s\"></div></td>" \ + "<td class=\"progressbar\"><div class=\"diff_goal\">%.2fâ¬</div><div class=\"progress surplus\" style=\"width: %s\"></div></td></tr>" % ( + title, earned, time_progress_indicator, success_color(success), int(success_income_cut * 100), diff_goal, int(success_income_bonus * 100)) + day_income = (db["workday_minutes_worked_1"] / 60.0) * db["workday_hourly_rate_1"] + day_income += (db["workday_minutes_worked_2"] / 60.0) * db["workday_hourly_rate_2"] + day_income += (db["workday_minutes_worked_3"] / 60.0) * db["workday_hourly_rate_3"] + year_plus = db["year_income"] + day_income + month_plus = db["month_income"] + day_income + week_plus = db["week_income"] + day_income + progress_time_year = day_of_year / year_length + progress_time_month = today.day / calendar.monthrange(today.year, today.month)[1] + progress_time_week = today.weekday() / 7 + year_line = progressbar("year", year_plus, db["year_goal"], progress_time_year) + month_line = progressbar("month", month_plus, month_goal, progress_time_month) + week_line = progressbar("week", week_plus, week_goal, progress_time_week) + day_line = progressbar("workday", day_income, workday_goal) + body = year_line + "\n" + month_line + "\n" + week_line + "\n" + day_line + page = header + body + footer % ( + db["workday_hourly_rate_1"], db["workday_minutes_worked_1"], + db["workday_hourly_rate_2"], db["workday_minutes_worked_2"], + db["workday_hourly_rate_3"], db["workday_minutes_worked_3"], + db["year_goal"], + month_goal, + week_goal, + db["workdays_per_month"], + workday_goal, + workdays_per_week, + ) + self.wfile.write(bytes(page, "utf-8")) + +if __name__ == "__main__": + webServer = HTTPServer((hostName, serverPort), MyServer) + print("Server started http://%s:%s" % (hostName, serverPort)) + try: + webServer.serve_forever() + except KeyboardInterrupt: + pass + webServer.server_close() + print("Server stopped.") -- 2.30.2