home · contact · privacy
Improve ledger.py.
[misc] / income_progress_bars.py
1 from http.server import BaseHTTPRequestHandler, HTTPServer
2 import os
3 import json
4
5 hostName = "localhost"
6 serverPort = 8081
7
8 header = """<html>
9 <meta charset="UTF-8">
10 <style>
11 body {
12   font-family: monospace;
13   background-color: #e0e0ff;
14 }
15 .countable {
16   font-family: monospace;
17   text-align: right;
18 }
19 td, th {
20   border: 1px solid black;
21 }
22 .progressbar {
23   width: 100px;
24   height: 20px;
25   background-color: black;
26   border: none;
27 }
28 .surplusbar {
29   width: 200px;
30 }
31 .time_progress {
32   position: absolute;
33   height: 20px;
34   background-color: white;
35   width: 2px;
36   border-left: 1px solid black; 
37   border-right: 1px solid black; 
38   z-index: 2;
39 }
40 .progress {
41   height: 20px;
42   z-index: 1;
43 }
44 .surplus {
45   background-color: green;
46 }
47 input {
48   font-family: monospace;
49   text-align: right;
50 }
51 input.rate {
52   text-align: center;
53   width: 4em;
54 }
55 input.minutes {
56   width: 4em;
57 }
58 input.workdays {
59   width: 3em;
60 }
61 input.year_goal {
62   width: 5em;
63 }
64 .diff_goal {
65   position: absolute;
66   width: 7em;
67   text-align: right;
68   color: white;
69   z-index: 3;
70 }
71 table {
72   margin-bottom: 2em;
73 }
74 .input_container {
75   text-align: center;
76 }
77 </style>
78 <body>
79 <table>
80 <tr><th /><th>earned</th><th>progress</th><th>surplus</th></tr >
81 """
82 footer = """</table>
83 <form action="/" method="POST">
84 <table>
85 <tr><th>hourly rate</th><th>worked today</th></tr>
86 <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>
87 <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>
88 <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>
89 <table>
90 <tr><th>yearly income goal</th><td><input type="number" class="year_goal" min="1" name="year_goal" value="%s" />€</td></tr>
91 <tr><th>monthly income goal</th><td class="countable">%.2f€</td></tr>
92 <tr><th>weekly income goal</th><td class="countable">%.2f€</td></tr>
93 <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>
94 <tr><th>workday income goal</th><td class="countable">%.2f€</td></tr>
95 <tr><th>workdays per week</th><td class="countable">%.2f€</td></tr>
96 </table>
97 <input type="submit" name="update" value="update inputs" />
98 <input type="submit" name="finish" value="finish day" />
99 </form>
100 </body
101 </html>"""
102
103 db_default = {
104   "timestamp_year": 0,
105   "timestamp_month": 0,
106   "timestamp_week": 0,
107   "year_income": 0,
108   "month_income": 0,
109   "week_income": 0,
110   "workday_hourly_rate_1": 10,
111   "workday_minutes_worked_1": 0,
112   "workday_hourly_rate_2": 25,
113   "workday_minutes_worked_2": 0,
114   "workday_hourly_rate_3": 50,
115   "workday_minutes_worked_3": 0,
116   "year_goal": 20000,
117   "workdays_per_month": 16 
118 }
119 db_file = "db.json"
120 lock_file = "db.lock"
121 def load_db():
122     if os.path.exists(db_file):
123         with open(db_file, "r") as f:
124             return json.load(f)
125     else:
126         return db_default
127
128 class MyServer(BaseHTTPRequestHandler):
129
130     def do_POST(self):
131         from urllib.parse import parse_qs
132         length = int(self.headers['content-length'])
133         postvars = parse_qs(self.rfile.read(length), keep_blank_values=1)
134         db = load_db()
135         db["workday_minutes_worked_1"] = int(postvars[b'workday_minutes_worked_1'][0].decode()) 
136         db["workday_minutes_worked_2"] = int(postvars[b'workday_minutes_worked_2'][0].decode()) 
137         db["workday_minutes_worked_3"] = int(postvars[b'workday_minutes_worked_3'][0].decode()) 
138         db["workday_hourly_rate_1"] = int(postvars[b'workday_hourly_rate_1'][0].decode()) 
139         db["workday_hourly_rate_2"] = int(postvars[b'workday_hourly_rate_2'][0].decode()) 
140         db["workday_hourly_rate_3"] = int(postvars[b'workday_hourly_rate_3'][0].decode()) 
141         db["year_goal"] = int(postvars[b'year_goal'][0].decode()) 
142         db["workdays_per_month"] = int(postvars[b'workdays_per_month'][0].decode()) 
143         if b'finish' in postvars.keys():
144             day_income = (db["workday_minutes_worked_1"] / 60.0) * db["workday_hourly_rate_1"] 
145             day_income += (db["workday_minutes_worked_2"] / 60.0) * db["workday_hourly_rate_2"] 
146             day_income += (db["workday_minutes_worked_3"] / 60.0) * db["workday_hourly_rate_3"] 
147             db["year_income"] += day_income 
148             db["month_income"] += day_income 
149             db["week_income"] += day_income 
150             db["workday_minutes_worked_1"] = 0
151             db["workday_minutes_worked_2"] = 0
152             db["workday_minutes_worked_3"] = 0
153         if not os.path.exists(lock_file):
154             with open(lock_file, "w+"): pass
155             with open(db_file, "w") as f:
156                 json.dump(db, f)
157             os.remove(lock_file)
158             self.send_response(302)
159             self.send_header('Location', '/')
160             self.end_headers()
161         else:
162             self.send_response(400)
163             self.end_headers()
164             self.wfile.write(bytes("Sorry, lock file!", "utf-8"))
165
166     def do_GET(self):
167         import datetime
168         import calendar 
169         self.send_response(200)
170         self.send_header("Content-type", "text/html")
171         self.end_headers()
172         db = load_db()
173         today = datetime.datetime.now()
174         if not os.path.exists(lock_file):
175             update_db = False
176             if today.year != db["timestamp_year"]:
177                 db["timestamp_year"] = today.year
178                 db["year_income"] = 0
179                 update_db = True 
180             if today.month != db["timestamp_month"]:
181                 db["timestamp_month"] = today.month
182                 db["month_income"] = 0
183                 update_db = True 
184             if today.isocalendar()[1] != db["timestamp_week"]:
185                 db["timestamp_week"] = today.isocalendar()[1]
186                 db["week_income"] = 0
187                 update_db = True 
188             if update_db:
189                 print("Resetting timestamp")
190                 with open(lock_file, "w+"): pass
191                 with open(db_file, "w") as f:
192                     json.dump(db, f)
193                 os.remove(lock_file)
194         else:
195             self.send_response(400)
196             self.end_headers()
197             self.wfile.write(bytes("Sorry, lock file!", "utf-8"))
198             return
199         day_of_year = today.toordinal() - datetime.date(today.year, 1, 1).toordinal() + 1
200         year_length = 365 + calendar.isleap(today.year)
201         workday_goal = db["year_goal"] / 12 / db["workdays_per_month"] 
202         workdays_per_week = (db["workdays_per_month"] * 12) / (year_length / 7) 
203         month_goal = db["year_goal"] / 12 
204         week_goal = db["year_goal"] / (year_length / 7) 
205         def success_color(success):
206             if success < 0.5:
207                 return "red";
208             elif success < 1:
209                 return "yellow";
210             else: 
211                 return "green"
212         def progressbar(title, earned, goal, time_progress=-1):
213             time_progress_indicator = ""
214             success_income = earned / goal 
215             success_income_cut = min(success_income, 1.0) 
216             success_income_bonus = max(success_income - 1.0, 0) 
217             success = success_income + 0
218             diff_goal = "%.2f€" % (earned - goal)
219             if title != "workday":
220                 diff_goal += "(%.2f€)" % (earned - (goal * time_progress))
221             if time_progress >= 0:
222                 success = 1
223                 if time_progress > 0:
224                     success = success_income / time_progress
225                 time_progress_indicator = "<div class=\"time_progress\" style=\"margin-left: %spx\"></div>" % int(time_progress * 100) 
226             return "<tr><th>%s</th>" \
227                     "<td class=\"countable\">%.2f€</td>" \
228                     "<td class=\"progressbar\">%s<div class=\"progress\" style=\"background-color: %s; width: %s\"></div></td>" \
229                     "<td class=\"progressbar surplusbar\"><div class=\"diff_goal\">%s</div><div class=\"progress surplus\" style=\"width: %s\"></div></td></tr>" % (
230                     title, earned, time_progress_indicator, success_color(success), int(success_income_cut * 100), diff_goal, int(success_income_bonus * 100))
231         day_income = (db["workday_minutes_worked_1"] / 60.0) * db["workday_hourly_rate_1"] 
232         day_income += (db["workday_minutes_worked_2"] / 60.0) * db["workday_hourly_rate_2"] 
233         day_income += (db["workday_minutes_worked_3"] / 60.0) * db["workday_hourly_rate_3"] 
234         year_plus = db["year_income"] + day_income 
235         month_plus = db["month_income"] + day_income 
236         week_plus = db["week_income"] + day_income
237         progress_time_year = day_of_year / year_length 
238         progress_time_month = today.day / calendar.monthrange(today.year, today.month)[1] 
239         progress_time_week = today.weekday() / 7 
240         year_line = progressbar("year", year_plus, db["year_goal"], progress_time_year)
241         month_line = progressbar("month", month_plus, month_goal, progress_time_month)
242         week_line = progressbar("week", week_plus, week_goal, progress_time_week)
243         day_line = progressbar("workday", day_income, workday_goal)
244         body = year_line + "\n" + month_line + "\n" + week_line + "\n" + day_line
245         page = header + body + footer % (
246                 db["workday_hourly_rate_1"], db["workday_minutes_worked_1"],
247                 db["workday_hourly_rate_2"], db["workday_minutes_worked_2"],
248                 db["workday_hourly_rate_3"], db["workday_minutes_worked_3"],
249                 db["year_goal"],
250                 month_goal,
251                 week_goal,
252                 db["workdays_per_month"],
253                 workday_goal,
254                 workdays_per_week,
255                 )
256         self.wfile.write(bytes(page, "utf-8"))
257
258 if __name__ == "__main__":        
259     webServer = HTTPServer((hostName, serverPort), MyServer)
260     print(f"Server started http://{hostName}:{serverPort}")
261     try:
262         webServer.serve_forever()
263     except KeyboardInterrupt:
264         pass
265     webServer.server_close()
266     print("Server stopped.")