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