home · contact · privacy
Refactor all accounting scripts.
[misc] / income_progress_bars.py
1 import os
2 import json
3 import jinja2
4 from plomlib import PlomDB, PlomException, run_server, run_server, PlomServer
5
6 server_port = 8083
7 db_path = '/home/plom/org/income.json'
8
9 tmpl = """
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><th>earned</th><th>progress</th><th>surplus</th></tr >
81 {% for p in progress_bars %}<tr><th>{{p.title}}</th>
82 <td class="countable">{{p.earned|round(2)}}</td>
83 <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}}px"></div></td>
84 <td class="progressbar surplusbar"><div class="diff_goal">{{p.diff_goal}}</div><div class="progressbar surplus" style="width: {{p.success_income_bonus}}px" ></div></td></tr>
85 {% endfor %}</table>
86
87 <form action="/" method="POST">
88 <table>
89 <tr><th>hourly rate</th><th>worked today</th></tr>
90 <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>
91 <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>
92 <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>
93 </table>
94 <table>
95 <tr><th>yearly income goal</th><td><input type="number" class="year_goal" min="1" name="year_goal" value="{{year_goal}}" />€</td></tr>
96 <tr><th>monthly income goal</th><td class="countable">{{month_goal|round(2)}}€</td></tr>
97 <tr><th>weekly income goal</th><td class="countable">{{week_goal|round(2)}}€</td></tr>
98 <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>
99 <tr><th>workday income goal</th><td class="countable">{{workday_goal|round(2)}}€</td></tr>
100 <tr><th>workdays per week</th><td class="countable">{{workdays_per_week|round(2)}}</td></tr>
101 </table>
102 <input type="submit" name="update" value="update inputs" />
103 <input type="submit" name="finish" value="finish day" />
104 </form>
105 """
106
107
108 class IncomeDB(PlomDB):
109
110     def __init__(self):
111         # defaults
112         self.timestamp_year = 0,
113         self.timestamp_month = 0,
114         self.timestamp_week = 0,
115         self.year_income = 0,
116         self.month_income = 0,
117         self.week_income = 0,
118         self.workday_hourly_rate_1 = 10,
119         self.workday_hourly_rate_2 = 25,
120         self.workday_hourly_rate_3 = 50,
121         self.workday_minutes_worked_1 = 0,
122         self.workday_minutes_worked_2 = 0,
123         self.workday_minutes_worked_3 = 0,
124         self.year_goal = 20000,
125         self.workdays_per_month = 16
126         super().__init__(db_path)
127
128     def read_db_file(self, f):
129         d = json.load(f)
130         for k, v in d.items():
131             if not hasattr(self, k):
132                 raise PlomException("bad key in db: " + k)
133             setattr(self, k, v)
134
135     def to_dict(self):
136         keys = [k for k in dir(self) if (not k.startswith('_')) and (not callable(getattr(self, k)))]
137         d = {}
138         for k in keys:
139             d[k] = getattr(self, k)
140         return d
141
142     def write_db(self):
143         self.write_text_to_db(json.dumps(self.to_dict()))
144
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
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.redirect()
192         except PlomException as e:
193             self.fail_400(e) 
194
195     def do_GET(self):
196         import datetime
197         import calendar
198         try:
199             db = IncomeDB()
200             today = datetime.datetime.now()
201             update_db = False
202             if today.year != db.timestamp_year:
203                 db.timestamp_year = today.year
204                 db.timestamp_month = today.month
205                 db.year_income = 0
206                 db.month_income = 0
207                 update_db = True
208             if today.month != db.timestamp_month:
209                 db.timestamp_month = today.month
210                 db.month_income = 0
211                 update_db = True
212             if today.isocalendar()[1] != db.timestamp_week:
213                 db.timestamp_week = today.isocalendar()[1]
214                 db.week_income = 0
215                 update_db = True
216             if update_db:
217                 print("Resetting timestamp")
218                 db.write_db()
219             day_of_year = today.toordinal() - datetime.date(today.year, 1, 1).toordinal() + 1
220             year_length = 365 + calendar.isleap(today.year)
221             workday_goal = db.year_goal / 12 / db.workdays_per_month
222             workdays_per_week = (db.workdays_per_month * 12) / (year_length / 7)
223             month_goal = db.year_goal / 12
224             week_goal = db.year_goal / (year_length / 7)
225             day_income = (db.workday_minutes_worked_1 / 60.0) * db.workday_hourly_rate_1
226             day_income += (db.workday_minutes_worked_2 / 60.0) * db.workday_hourly_rate_2
227             day_income += (db.workday_minutes_worked_3 / 60.0) * db.workday_hourly_rate_3
228             year_plus = db.year_income + day_income
229             month_plus = db.month_income + day_income
230             week_plus = db.week_income + day_income
231             progress_time_year = day_of_year / year_length
232             progress_time_month = today.day / calendar.monthrange(today.year, today.month)[1]
233             progress_time_week = today.weekday() / 7
234             progress_bars = [ProgressBar("year", year_plus, db.year_goal, progress_time_year),
235                              ProgressBar("month", month_plus, month_goal, progress_time_month),
236                              ProgressBar("week", week_plus, week_goal, progress_time_week),
237                              ProgressBar("workday", day_income, workday_goal)]
238             page = jinja2.Template(tmpl).render(
239                     progress_bars = progress_bars,
240                     workday_hourly_rate_1 = db.workday_hourly_rate_1,
241                     workday_minutes_worked_1 = db.workday_minutes_worked_1,
242                     workday_hourly_rate_2 = db.workday_hourly_rate_2,
243                     workday_minutes_worked_2 = db.workday_minutes_worked_2,
244                     workday_hourly_rate_3 = db.workday_hourly_rate_3,
245                     workday_minutes_worked_3 = db.workday_minutes_worked_3,
246                     year_goal = db.year_goal,
247                     month_goal = month_goal,
248                     week_goal = week_goal,
249                     workdays_per_month = db.workdays_per_month,
250                     workday_goal = workday_goal,
251                     workdays_per_week = workdays_per_week,
252                     )
253             self.send_HTML(page)
254         except PlomException as e:
255             self.fail_400(e) 
256
257
258 if __name__ == "__main__":       
259     run_server(server_port, IncomeServer)