home · contact · privacy
Add income tracker.
authorChristian Heller <c.heller@plomlompom.de>
Mon, 26 Dec 2022 16:06:33 +0000 (17:06 +0100)
committerChristian Heller <c.heller@plomlompom.de>
Mon, 26 Dec 2022 16:06:33 +0000 (17:06 +0100)
income_progress_bars.py [new file with mode: 0644]

diff --git a/income_progress_bars.py b/income_progress_bars.py
new file mode 100644 (file)
index 0000000..48b1179
--- /dev/null
@@ -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.")