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