@@ 12-435 (lines=424) @@ | ||
9 | from core.useractivity import access_control, api_key_control |
|
10 | ||
11 | ||
12 | class Reporting: |
|
13 | def __init__(self): |
|
14 | """"Initializes Reporting""" |
|
15 | pass |
|
16 | ||
17 | @staticmethod |
|
18 | def on_options(req, resp): |
|
19 | _ = req |
|
20 | resp.status = falcon.HTTP_200 |
|
21 | ||
22 | #################################################################################################################### |
|
23 | # PROCEDURES |
|
24 | # Step 1: valid parameters |
|
25 | # Step 2: query the virtual meter and energy category |
|
26 | # Step 3: query base period energy consumption |
|
27 | # Step 4: query base period energy carbon dioxide emissions |
|
28 | # Step 5: query reporting period energy consumption |
|
29 | # Step 6: query reporting period energy carbon dioxide emissions |
|
30 | # Step 7: query tariff data |
|
31 | # Step 8: construct the report |
|
32 | #################################################################################################################### |
|
33 | @staticmethod |
|
34 | def on_get(req, resp): |
|
35 | if 'API-KEY' not in req.headers or \ |
|
36 | not isinstance(req.headers['API-KEY'], str) or \ |
|
37 | len(str.strip(req.headers['API-KEY'])) == 0: |
|
38 | access_control(req) |
|
39 | else: |
|
40 | api_key_control(req) |
|
41 | print(req.params) |
|
42 | virtual_meter_id = req.params.get('virtualmeterid') |
|
43 | period_type = req.params.get('periodtype') |
|
44 | base_period_start_datetime_local = req.params.get('baseperiodstartdatetime') |
|
45 | base_period_end_datetime_local = req.params.get('baseperiodenddatetime') |
|
46 | reporting_period_start_datetime_local = req.params.get('reportingperiodstartdatetime') |
|
47 | reporting_period_end_datetime_local = req.params.get('reportingperiodenddatetime') |
|
48 | language = req.params.get('language') |
|
49 | quick_mode = req.params.get('quickmode') |
|
50 | ||
51 | ################################################################################################################ |
|
52 | # Step 1: valid parameters |
|
53 | ################################################################################################################ |
|
54 | if virtual_meter_id is None: |
|
55 | raise falcon.HTTPError(status=falcon.HTTP_400, |
|
56 | title='API.BAD_REQUEST', |
|
57 | description='API.INVALID_VIRTUAL_METER_ID') |
|
58 | else: |
|
59 | virtual_meter_id = str.strip(virtual_meter_id) |
|
60 | if not virtual_meter_id.isdigit() or int(virtual_meter_id) <= 0: |
|
61 | raise falcon.HTTPError(status=falcon.HTTP_400, |
|
62 | title='API.BAD_REQUEST', |
|
63 | description='API.INVALID_VIRTUAL_METER_ID') |
|
64 | ||
65 | if period_type is None: |
|
66 | raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST', |
|
67 | description='API.INVALID_PERIOD_TYPE') |
|
68 | else: |
|
69 | period_type = str.strip(period_type) |
|
70 | if period_type not in ['hourly', 'daily', 'weekly', 'monthly', 'yearly']: |
|
71 | raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST', |
|
72 | description='API.INVALID_PERIOD_TYPE') |
|
73 | ||
74 | timezone_offset = int(config.utc_offset[1:3]) * 60 + int(config.utc_offset[4:6]) |
|
75 | if config.utc_offset[0] == '-': |
|
76 | timezone_offset = -timezone_offset |
|
77 | ||
78 | base_start_datetime_utc = None |
|
79 | if base_period_start_datetime_local is not None and len(str.strip(base_period_start_datetime_local)) > 0: |
|
80 | base_period_start_datetime_local = str.strip(base_period_start_datetime_local) |
|
81 | try: |
|
82 | base_start_datetime_utc = datetime.strptime(base_period_start_datetime_local, '%Y-%m-%dT%H:%M:%S') |
|
83 | except ValueError: |
|
84 | raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST', |
|
85 | description="API.INVALID_BASE_PERIOD_START_DATETIME") |
|
86 | base_start_datetime_utc = \ |
|
87 | base_start_datetime_utc.replace(tzinfo=timezone.utc) - timedelta(minutes=timezone_offset) |
|
88 | # nomalize the start datetime |
|
89 | if config.minutes_to_count == 30 and base_start_datetime_utc.minute >= 30: |
|
90 | base_start_datetime_utc = base_start_datetime_utc.replace(minute=30, second=0, microsecond=0) |
|
91 | else: |
|
92 | base_start_datetime_utc = base_start_datetime_utc.replace(minute=0, second=0, microsecond=0) |
|
93 | ||
94 | base_end_datetime_utc = None |
|
95 | if base_period_end_datetime_local is not None and len(str.strip(base_period_end_datetime_local)) > 0: |
|
96 | base_period_end_datetime_local = str.strip(base_period_end_datetime_local) |
|
97 | try: |
|
98 | base_end_datetime_utc = datetime.strptime(base_period_end_datetime_local, '%Y-%m-%dT%H:%M:%S') |
|
99 | except ValueError: |
|
100 | raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST', |
|
101 | description="API.INVALID_BASE_PERIOD_END_DATETIME") |
|
102 | base_end_datetime_utc = \ |
|
103 | base_end_datetime_utc.replace(tzinfo=timezone.utc) - timedelta(minutes=timezone_offset) |
|
104 | ||
105 | if base_start_datetime_utc is not None and base_end_datetime_utc is not None and \ |
|
106 | base_start_datetime_utc >= base_end_datetime_utc: |
|
107 | raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST', |
|
108 | description='API.INVALID_BASE_PERIOD_END_DATETIME') |
|
109 | ||
110 | if reporting_period_start_datetime_local is None: |
|
111 | raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST', |
|
112 | description="API.INVALID_REPORTING_PERIOD_START_DATETIME") |
|
113 | else: |
|
114 | reporting_period_start_datetime_local = str.strip(reporting_period_start_datetime_local) |
|
115 | try: |
|
116 | reporting_start_datetime_utc = datetime.strptime(reporting_period_start_datetime_local, |
|
117 | '%Y-%m-%dT%H:%M:%S') |
|
118 | except ValueError: |
|
119 | raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST', |
|
120 | description="API.INVALID_REPORTING_PERIOD_START_DATETIME") |
|
121 | reporting_start_datetime_utc = \ |
|
122 | reporting_start_datetime_utc.replace(tzinfo=timezone.utc) - timedelta(minutes=timezone_offset) |
|
123 | # nomalize the start datetime |
|
124 | if config.minutes_to_count == 30 and reporting_start_datetime_utc.minute >= 30: |
|
125 | reporting_start_datetime_utc = reporting_start_datetime_utc.replace(minute=30, second=0, microsecond=0) |
|
126 | else: |
|
127 | reporting_start_datetime_utc = reporting_start_datetime_utc.replace(minute=0, second=0, microsecond=0) |
|
128 | ||
129 | if reporting_period_end_datetime_local is None: |
|
130 | raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST', |
|
131 | description="API.INVALID_REPORTING_PERIOD_END_DATETIME") |
|
132 | else: |
|
133 | reporting_period_end_datetime_local = str.strip(reporting_period_end_datetime_local) |
|
134 | try: |
|
135 | reporting_end_datetime_utc = datetime.strptime(reporting_period_end_datetime_local, |
|
136 | '%Y-%m-%dT%H:%M:%S') |
|
137 | except ValueError: |
|
138 | raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST', |
|
139 | description="API.INVALID_REPORTING_PERIOD_END_DATETIME") |
|
140 | reporting_end_datetime_utc = reporting_end_datetime_utc.replace(tzinfo=timezone.utc) - \ |
|
141 | timedelta(minutes=timezone_offset) |
|
142 | ||
143 | if reporting_start_datetime_utc >= reporting_end_datetime_utc: |
|
144 | raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST', |
|
145 | description='API.INVALID_REPORTING_PERIOD_END_DATETIME') |
|
146 | ||
147 | # if turn quick mode on, do not return parameters data and excel file |
|
148 | is_quick_mode = False |
|
149 | if quick_mode is not None and \ |
|
150 | len(str.strip(quick_mode)) > 0 and \ |
|
151 | str.lower(str.strip(quick_mode)) in ('true', 't', 'on', 'yes', 'y'): |
|
152 | is_quick_mode = True |
|
153 | ||
154 | trans = utilities.get_translation(language) |
|
155 | trans.install() |
|
156 | _ = trans.gettext |
|
157 | ||
158 | ################################################################################################################ |
|
159 | # Step 2: query the virtual meter and energy category |
|
160 | ################################################################################################################ |
|
161 | cnx_system = mysql.connector.connect(**config.myems_system_db) |
|
162 | cursor_system = cnx_system.cursor() |
|
163 | ||
164 | cnx_energy = mysql.connector.connect(**config.myems_energy_db) |
|
165 | cursor_energy = cnx_energy.cursor() |
|
166 | ||
167 | cnx_carbon = mysql.connector.connect(**config.myems_carbon_db) |
|
168 | cursor_carbon = cnx_carbon.cursor() |
|
169 | ||
170 | cursor_system.execute(" SELECT m.id, m.name, m.cost_center_id, m.energy_category_id, " |
|
171 | " ec.name, ec.unit_of_measure, ec.kgce, ec.kgco2e " |
|
172 | " FROM tbl_virtual_meters m, tbl_energy_categories ec " |
|
173 | " WHERE m.id = %s AND m.energy_category_id = ec.id ", (virtual_meter_id,)) |
|
174 | row_virtual_meter = cursor_system.fetchone() |
|
175 | if row_virtual_meter is None: |
|
176 | if cursor_system: |
|
177 | cursor_system.close() |
|
178 | if cnx_system: |
|
179 | cnx_system.close() |
|
180 | ||
181 | if cursor_energy: |
|
182 | cursor_energy.close() |
|
183 | if cnx_energy: |
|
184 | cnx_energy.close() |
|
185 | ||
186 | if cursor_carbon: |
|
187 | cursor_carbon.close() |
|
188 | if cnx_carbon: |
|
189 | cnx_carbon.close() |
|
190 | raise falcon.HTTPError(status=falcon.HTTP_404, title='API.NOT_FOUND', |
|
191 | description='API.VIRTUAL_METER_NOT_FOUND') |
|
192 | ||
193 | virtual_meter = dict() |
|
194 | virtual_meter['id'] = row_virtual_meter[0] |
|
195 | virtual_meter['name'] = row_virtual_meter[1] |
|
196 | virtual_meter['cost_center_id'] = row_virtual_meter[2] |
|
197 | virtual_meter['energy_category_id'] = row_virtual_meter[3] |
|
198 | virtual_meter['energy_category_name'] = row_virtual_meter[4] |
|
199 | virtual_meter['unit_of_measure'] = config.currency_unit |
|
200 | virtual_meter['kgce'] = row_virtual_meter[6] |
|
201 | virtual_meter['kgco2e'] = row_virtual_meter[7] |
|
202 | ||
203 | ################################################################################################################ |
|
204 | # Step 3: query base period energy consumption |
|
205 | ################################################################################################################ |
|
206 | query = (" SELECT start_datetime_utc, actual_value " |
|
207 | " FROM tbl_virtual_meter_hourly " |
|
208 | " WHERE virtual_meter_id = %s " |
|
209 | " AND start_datetime_utc >= %s " |
|
210 | " AND start_datetime_utc < %s " |
|
211 | " ORDER BY start_datetime_utc ") |
|
212 | cursor_energy.execute(query, (virtual_meter['id'], base_start_datetime_utc, base_end_datetime_utc)) |
|
213 | rows_virtual_meter_hourly = cursor_energy.fetchall() |
|
214 | ||
215 | rows_virtual_meter_periodically = utilities.aggregate_hourly_data_by_period(rows_virtual_meter_hourly, |
|
216 | base_start_datetime_utc, |
|
217 | base_end_datetime_utc, |
|
218 | period_type) |
|
219 | base = dict() |
|
220 | base['timestamps'] = list() |
|
221 | base['values'] = list() |
|
222 | base['total_in_category'] = Decimal(0.0) |
|
223 | base['total_in_kgce'] = Decimal(0.0) |
|
224 | base['total_in_kgco2e'] = Decimal(0.0) |
|
225 | ||
226 | for row_virtual_meter_periodically in rows_virtual_meter_periodically: |
|
227 | current_datetime_local = row_virtual_meter_periodically[0].replace(tzinfo=timezone.utc) + \ |
|
228 | timedelta(minutes=timezone_offset) |
|
229 | if period_type == 'hourly': |
|
230 | current_datetime = current_datetime_local.isoformat()[0:19] |
|
231 | elif period_type == 'daily': |
|
232 | current_datetime = current_datetime_local.isoformat()[0:10] |
|
233 | elif period_type == 'weekly': |
|
234 | current_datetime = current_datetime_local.isoformat()[0:10] |
|
235 | elif period_type == 'monthly': |
|
236 | current_datetime = current_datetime_local.isoformat()[0:7] |
|
237 | elif period_type == 'yearly': |
|
238 | current_datetime = current_datetime_local.isoformat()[0:4] |
|
239 | ||
240 | actual_value = Decimal(0.0) if row_virtual_meter_periodically[1] is None \ |
|
241 | else row_virtual_meter_periodically[1] |
|
242 | base['timestamps'].append(current_datetime) |
|
243 | base['total_in_kgce'] += actual_value * virtual_meter['kgce'] |
|
244 | base['total_in_kgco2e'] += actual_value * virtual_meter['kgco2e'] |
|
245 | ||
246 | ################################################################################################################ |
|
247 | # Step 4: query base period energy carbon dioxide emissions |
|
248 | ################################################################################################################ |
|
249 | query = (" SELECT start_datetime_utc, actual_value " |
|
250 | " FROM tbl_virtual_meter_hourly " |
|
251 | " WHERE virtual_meter_id = %s " |
|
252 | " AND start_datetime_utc >= %s " |
|
253 | " AND start_datetime_utc < %s " |
|
254 | " ORDER BY start_datetime_utc ") |
|
255 | cursor_carbon.execute(query, (virtual_meter['id'], base_start_datetime_utc, base_end_datetime_utc)) |
|
256 | rows_virtual_meter_hourly = cursor_carbon.fetchall() |
|
257 | ||
258 | rows_virtual_meter_periodically = utilities.aggregate_hourly_data_by_period(rows_virtual_meter_hourly, |
|
259 | base_start_datetime_utc, |
|
260 | base_end_datetime_utc, |
|
261 | period_type) |
|
262 | ||
263 | base['values'] = list() |
|
264 | base['total_in_category'] = Decimal(0.0) |
|
265 | ||
266 | for row_virtual_meter_periodically in rows_virtual_meter_periodically: |
|
267 | actual_value = Decimal(0.0) if row_virtual_meter_periodically[1] is None \ |
|
268 | else row_virtual_meter_periodically[1] |
|
269 | base['values'].append(actual_value) |
|
270 | base['total_in_category'] += actual_value |
|
271 | ||
272 | ################################################################################################################ |
|
273 | # Step 5: query reporting period energy consumption |
|
274 | ################################################################################################################ |
|
275 | query = (" SELECT start_datetime_utc, actual_value " |
|
276 | " FROM tbl_virtual_meter_hourly " |
|
277 | " WHERE virtual_meter_id = %s " |
|
278 | " AND start_datetime_utc >= %s " |
|
279 | " AND start_datetime_utc < %s " |
|
280 | " ORDER BY start_datetime_utc ") |
|
281 | cursor_energy.execute(query, (virtual_meter['id'], reporting_start_datetime_utc, reporting_end_datetime_utc)) |
|
282 | rows_virtual_meter_hourly = cursor_energy.fetchall() |
|
283 | ||
284 | rows_virtual_meter_periodically = utilities.aggregate_hourly_data_by_period(rows_virtual_meter_hourly, |
|
285 | reporting_start_datetime_utc, |
|
286 | reporting_end_datetime_utc, |
|
287 | period_type) |
|
288 | reporting = dict() |
|
289 | reporting['timestamps'] = list() |
|
290 | reporting['values'] = list() |
|
291 | reporting['rates'] = list() |
|
292 | reporting['total_in_category'] = Decimal(0.0) |
|
293 | reporting['total_in_kgce'] = Decimal(0.0) |
|
294 | reporting['total_in_kgco2e'] = Decimal(0.0) |
|
295 | ||
296 | for row_virtual_meter_periodically in rows_virtual_meter_periodically: |
|
297 | current_datetime_local = row_virtual_meter_periodically[0].replace(tzinfo=timezone.utc) + \ |
|
298 | timedelta(minutes=timezone_offset) |
|
299 | if period_type == 'hourly': |
|
300 | current_datetime = current_datetime_local.isoformat()[0:19] |
|
301 | elif period_type == 'daily': |
|
302 | current_datetime = current_datetime_local.isoformat()[0:10] |
|
303 | elif period_type == 'weekly': |
|
304 | current_datetime = current_datetime_local.isoformat()[0:10] |
|
305 | elif period_type == 'monthly': |
|
306 | current_datetime = current_datetime_local.isoformat()[0:7] |
|
307 | elif period_type == 'yearly': |
|
308 | current_datetime = current_datetime_local.isoformat()[0:4] |
|
309 | ||
310 | actual_value = Decimal(0.0) if row_virtual_meter_periodically[1] is None \ |
|
311 | else row_virtual_meter_periodically[1] |
|
312 | ||
313 | reporting['timestamps'].append(current_datetime) |
|
314 | reporting['total_in_kgce'] += actual_value * virtual_meter['kgce'] |
|
315 | reporting['total_in_kgco2e'] += actual_value * virtual_meter['kgco2e'] |
|
316 | ||
317 | ################################################################################################################ |
|
318 | # Step 6: query reporting period energy carbon dioxide emissions |
|
319 | ################################################################################################################ |
|
320 | query = (" SELECT start_datetime_utc, actual_value " |
|
321 | " FROM tbl_virtual_meter_hourly " |
|
322 | " WHERE virtual_meter_id = %s " |
|
323 | " AND start_datetime_utc >= %s " |
|
324 | " AND start_datetime_utc < %s " |
|
325 | " ORDER BY start_datetime_utc ") |
|
326 | cursor_carbon.execute(query, (virtual_meter['id'], reporting_start_datetime_utc, reporting_end_datetime_utc)) |
|
327 | rows_virtual_meter_hourly = cursor_carbon.fetchall() |
|
328 | ||
329 | rows_virtual_meter_periodically = utilities.aggregate_hourly_data_by_period(rows_virtual_meter_hourly, |
|
330 | reporting_start_datetime_utc, |
|
331 | reporting_end_datetime_utc, |
|
332 | period_type) |
|
333 | ||
334 | for row_virtual_meter_periodically in rows_virtual_meter_periodically: |
|
335 | actual_value = Decimal(0.0) if row_virtual_meter_periodically[1] is None \ |
|
336 | else row_virtual_meter_periodically[1] |
|
337 | ||
338 | reporting['values'].append(actual_value) |
|
339 | reporting['total_in_category'] += actual_value |
|
340 | ||
341 | for index, value in enumerate(reporting['values']): |
|
342 | if index < len(base['values']) and base['values'][index] != 0 and value != 0: |
|
343 | reporting['rates'].append((value - base['values'][index]) / base['values'][index]) |
|
344 | else: |
|
345 | reporting['rates'].append(None) |
|
346 | ||
347 | ################################################################################################################ |
|
348 | # Step 7: query tariff data |
|
349 | ################################################################################################################ |
|
350 | parameters_data = dict() |
|
351 | parameters_data['names'] = list() |
|
352 | parameters_data['timestamps'] = list() |
|
353 | parameters_data['values'] = list() |
|
354 | if config.is_tariff_appended and not is_quick_mode: |
|
355 | tariff_dict = utilities.get_energy_category_tariffs(virtual_meter['cost_center_id'], |
|
356 | virtual_meter['energy_category_id'], |
|
357 | reporting_start_datetime_utc, |
|
358 | reporting_end_datetime_utc) |
|
359 | tariff_timestamp_list = list() |
|
360 | tariff_value_list = list() |
|
361 | for k, v in tariff_dict.items(): |
|
362 | # convert k from utc to local |
|
363 | k = k + timedelta(minutes=timezone_offset) |
|
364 | tariff_timestamp_list.append(k.isoformat()[0:19]) |
|
365 | tariff_value_list.append(v) |
|
366 | ||
367 | parameters_data['names'].append(_('Tariff') + '-' + virtual_meter['energy_category_name']) |
|
368 | parameters_data['timestamps'].append(tariff_timestamp_list) |
|
369 | parameters_data['values'].append(tariff_value_list) |
|
370 | ||
371 | ################################################################################################################ |
|
372 | # Step 8: construct the report |
|
373 | ################################################################################################################ |
|
374 | if cursor_system: |
|
375 | cursor_system.close() |
|
376 | if cnx_system: |
|
377 | cnx_system.close() |
|
378 | ||
379 | if cursor_energy: |
|
380 | cursor_energy.close() |
|
381 | if cnx_energy: |
|
382 | cnx_energy.close() |
|
383 | ||
384 | if cursor_carbon: |
|
385 | cursor_carbon.close() |
|
386 | if cnx_carbon: |
|
387 | cnx_carbon.close() |
|
388 | ||
389 | result = { |
|
390 | "virtual_meter": { |
|
391 | "cost_center_id": virtual_meter['cost_center_id'], |
|
392 | "energy_category_id": virtual_meter['energy_category_id'], |
|
393 | "energy_category_name": virtual_meter['energy_category_name'], |
|
394 | "unit_of_measure": 'KG', |
|
395 | "kgce": virtual_meter['kgce'], |
|
396 | "kgco2e": virtual_meter['kgco2e'], |
|
397 | }, |
|
398 | "base_period": { |
|
399 | "total_in_category": base['total_in_category'], |
|
400 | "total_in_kgce": base['total_in_kgce'], |
|
401 | "total_in_kgco2e": base['total_in_kgco2e'], |
|
402 | "timestamps": base['timestamps'], |
|
403 | "values": base['values'], |
|
404 | }, |
|
405 | "reporting_period": { |
|
406 | "increment_rate": |
|
407 | (reporting['total_in_category'] - base['total_in_category']) / base['total_in_category'] |
|
408 | if base['total_in_category'] != Decimal(0.0) else None, |
|
409 | "total_in_category": reporting['total_in_category'], |
|
410 | "total_in_kgce": reporting['total_in_kgce'], |
|
411 | "total_in_kgco2e": reporting['total_in_kgco2e'], |
|
412 | "timestamps": reporting['timestamps'], |
|
413 | "values": reporting['values'], |
|
414 | "rates": reporting['rates'], |
|
415 | }, |
|
416 | "parameters": { |
|
417 | "names": parameters_data['names'], |
|
418 | "timestamps": parameters_data['timestamps'], |
|
419 | "values": parameters_data['values'] |
|
420 | }, |
|
421 | } |
|
422 | ||
423 | # export result to Excel file and then encode the file to base64 string |
|
424 | if not is_quick_mode: |
|
425 | result['excel_bytes_base64'] = \ |
|
426 | excelexporters.virtualmetercarbon.export(result, |
|
427 | virtual_meter['name'], |
|
428 | base_period_start_datetime_local, |
|
429 | base_period_end_datetime_local, |
|
430 | reporting_period_start_datetime_local, |
|
431 | reporting_period_end_datetime_local, |
|
432 | period_type, |
|
433 | language) |
|
434 | ||
435 | resp.text = json.dumps(result) |
|
436 |
@@ 12-435 (lines=424) @@ | ||
9 | from core.useractivity import access_control, api_key_control |
|
10 | ||
11 | ||
12 | class Reporting: |
|
13 | def __init__(self): |
|
14 | """"Initializes Reporting""" |
|
15 | pass |
|
16 | ||
17 | @staticmethod |
|
18 | def on_options(req, resp): |
|
19 | _ = req |
|
20 | resp.status = falcon.HTTP_200 |
|
21 | ||
22 | #################################################################################################################### |
|
23 | # PROCEDURES |
|
24 | # Step 1: valid parameters |
|
25 | # Step 2: query the virtual meter and energy category |
|
26 | # Step 3: query base period energy consumption |
|
27 | # Step 4: query base period energy cost |
|
28 | # Step 5: query reporting period energy consumption |
|
29 | # Step 6: query reporting period energy cost |
|
30 | # Step 7: query tariff data |
|
31 | # Step 8: construct the report |
|
32 | #################################################################################################################### |
|
33 | @staticmethod |
|
34 | def on_get(req, resp): |
|
35 | if 'API-KEY' not in req.headers or \ |
|
36 | not isinstance(req.headers['API-KEY'], str) or \ |
|
37 | len(str.strip(req.headers['API-KEY'])) == 0: |
|
38 | access_control(req) |
|
39 | else: |
|
40 | api_key_control(req) |
|
41 | print(req.params) |
|
42 | virtual_meter_id = req.params.get('virtualmeterid') |
|
43 | period_type = req.params.get('periodtype') |
|
44 | base_period_start_datetime_local = req.params.get('baseperiodstartdatetime') |
|
45 | base_period_end_datetime_local = req.params.get('baseperiodenddatetime') |
|
46 | reporting_period_start_datetime_local = req.params.get('reportingperiodstartdatetime') |
|
47 | reporting_period_end_datetime_local = req.params.get('reportingperiodenddatetime') |
|
48 | language = req.params.get('language') |
|
49 | quick_mode = req.params.get('quickmode') |
|
50 | ||
51 | ################################################################################################################ |
|
52 | # Step 1: valid parameters |
|
53 | ################################################################################################################ |
|
54 | if virtual_meter_id is None: |
|
55 | raise falcon.HTTPError(status=falcon.HTTP_400, |
|
56 | title='API.BAD_REQUEST', |
|
57 | description='API.INVALID_VIRTUAL_METER_ID') |
|
58 | else: |
|
59 | virtual_meter_id = str.strip(virtual_meter_id) |
|
60 | if not virtual_meter_id.isdigit() or int(virtual_meter_id) <= 0: |
|
61 | raise falcon.HTTPError(status=falcon.HTTP_400, |
|
62 | title='API.BAD_REQUEST', |
|
63 | description='API.INVALID_VIRTUAL_METER_ID') |
|
64 | ||
65 | if period_type is None: |
|
66 | raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST', |
|
67 | description='API.INVALID_PERIOD_TYPE') |
|
68 | else: |
|
69 | period_type = str.strip(period_type) |
|
70 | if period_type not in ['hourly', 'daily', 'weekly', 'monthly', 'yearly']: |
|
71 | raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST', |
|
72 | description='API.INVALID_PERIOD_TYPE') |
|
73 | ||
74 | timezone_offset = int(config.utc_offset[1:3]) * 60 + int(config.utc_offset[4:6]) |
|
75 | if config.utc_offset[0] == '-': |
|
76 | timezone_offset = -timezone_offset |
|
77 | ||
78 | base_start_datetime_utc = None |
|
79 | if base_period_start_datetime_local is not None and len(str.strip(base_period_start_datetime_local)) > 0: |
|
80 | base_period_start_datetime_local = str.strip(base_period_start_datetime_local) |
|
81 | try: |
|
82 | base_start_datetime_utc = datetime.strptime(base_period_start_datetime_local, '%Y-%m-%dT%H:%M:%S') |
|
83 | except ValueError: |
|
84 | raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST', |
|
85 | description="API.INVALID_BASE_PERIOD_START_DATETIME") |
|
86 | base_start_datetime_utc = \ |
|
87 | base_start_datetime_utc.replace(tzinfo=timezone.utc) - timedelta(minutes=timezone_offset) |
|
88 | # nomalize the start datetime |
|
89 | if config.minutes_to_count == 30 and base_start_datetime_utc.minute >= 30: |
|
90 | base_start_datetime_utc = base_start_datetime_utc.replace(minute=30, second=0, microsecond=0) |
|
91 | else: |
|
92 | base_start_datetime_utc = base_start_datetime_utc.replace(minute=0, second=0, microsecond=0) |
|
93 | ||
94 | base_end_datetime_utc = None |
|
95 | if base_period_end_datetime_local is not None and len(str.strip(base_period_end_datetime_local)) > 0: |
|
96 | base_period_end_datetime_local = str.strip(base_period_end_datetime_local) |
|
97 | try: |
|
98 | base_end_datetime_utc = datetime.strptime(base_period_end_datetime_local, '%Y-%m-%dT%H:%M:%S') |
|
99 | except ValueError: |
|
100 | raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST', |
|
101 | description="API.INVALID_BASE_PERIOD_END_DATETIME") |
|
102 | base_end_datetime_utc = \ |
|
103 | base_end_datetime_utc.replace(tzinfo=timezone.utc) - timedelta(minutes=timezone_offset) |
|
104 | ||
105 | if base_start_datetime_utc is not None and base_end_datetime_utc is not None and \ |
|
106 | base_start_datetime_utc >= base_end_datetime_utc: |
|
107 | raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST', |
|
108 | description='API.INVALID_BASE_PERIOD_END_DATETIME') |
|
109 | ||
110 | if reporting_period_start_datetime_local is None: |
|
111 | raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST', |
|
112 | description="API.INVALID_REPORTING_PERIOD_START_DATETIME") |
|
113 | else: |
|
114 | reporting_period_start_datetime_local = str.strip(reporting_period_start_datetime_local) |
|
115 | try: |
|
116 | reporting_start_datetime_utc = datetime.strptime(reporting_period_start_datetime_local, |
|
117 | '%Y-%m-%dT%H:%M:%S') |
|
118 | except ValueError: |
|
119 | raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST', |
|
120 | description="API.INVALID_REPORTING_PERIOD_START_DATETIME") |
|
121 | reporting_start_datetime_utc = \ |
|
122 | reporting_start_datetime_utc.replace(tzinfo=timezone.utc) - timedelta(minutes=timezone_offset) |
|
123 | # nomalize the start datetime |
|
124 | if config.minutes_to_count == 30 and reporting_start_datetime_utc.minute >= 30: |
|
125 | reporting_start_datetime_utc = reporting_start_datetime_utc.replace(minute=30, second=0, microsecond=0) |
|
126 | else: |
|
127 | reporting_start_datetime_utc = reporting_start_datetime_utc.replace(minute=0, second=0, microsecond=0) |
|
128 | ||
129 | if reporting_period_end_datetime_local is None: |
|
130 | raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST', |
|
131 | description="API.INVALID_REPORTING_PERIOD_END_DATETIME") |
|
132 | else: |
|
133 | reporting_period_end_datetime_local = str.strip(reporting_period_end_datetime_local) |
|
134 | try: |
|
135 | reporting_end_datetime_utc = datetime.strptime(reporting_period_end_datetime_local, |
|
136 | '%Y-%m-%dT%H:%M:%S') |
|
137 | except ValueError: |
|
138 | raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST', |
|
139 | description="API.INVALID_REPORTING_PERIOD_END_DATETIME") |
|
140 | reporting_end_datetime_utc = reporting_end_datetime_utc.replace(tzinfo=timezone.utc) - \ |
|
141 | timedelta(minutes=timezone_offset) |
|
142 | ||
143 | if reporting_start_datetime_utc >= reporting_end_datetime_utc: |
|
144 | raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST', |
|
145 | description='API.INVALID_REPORTING_PERIOD_END_DATETIME') |
|
146 | ||
147 | # if turn quick mode on, do not return parameters data and excel file |
|
148 | is_quick_mode = False |
|
149 | if quick_mode is not None and \ |
|
150 | len(str.strip(quick_mode)) > 0 and \ |
|
151 | str.lower(str.strip(quick_mode)) in ('true', 't', 'on', 'yes', 'y'): |
|
152 | is_quick_mode = True |
|
153 | ||
154 | trans = utilities.get_translation(language) |
|
155 | trans.install() |
|
156 | _ = trans.gettext |
|
157 | ||
158 | ################################################################################################################ |
|
159 | # Step 2: query the virtual meter and energy category |
|
160 | ################################################################################################################ |
|
161 | cnx_system = mysql.connector.connect(**config.myems_system_db) |
|
162 | cursor_system = cnx_system.cursor() |
|
163 | ||
164 | cnx_energy = mysql.connector.connect(**config.myems_energy_db) |
|
165 | cursor_energy = cnx_energy.cursor() |
|
166 | ||
167 | cnx_billing = mysql.connector.connect(**config.myems_billing_db) |
|
168 | cursor_billing = cnx_billing.cursor() |
|
169 | ||
170 | cursor_system.execute(" SELECT m.id, m.name, m.cost_center_id, m.energy_category_id, " |
|
171 | " ec.name, ec.unit_of_measure, ec.kgce, ec.kgco2e " |
|
172 | " FROM tbl_virtual_meters m, tbl_energy_categories ec " |
|
173 | " WHERE m.id = %s AND m.energy_category_id = ec.id ", (virtual_meter_id,)) |
|
174 | row_virtual_meter = cursor_system.fetchone() |
|
175 | if row_virtual_meter is None: |
|
176 | if cursor_system: |
|
177 | cursor_system.close() |
|
178 | if cnx_system: |
|
179 | cnx_system.close() |
|
180 | ||
181 | if cursor_energy: |
|
182 | cursor_energy.close() |
|
183 | if cnx_energy: |
|
184 | cnx_energy.close() |
|
185 | ||
186 | if cursor_billing: |
|
187 | cursor_billing.close() |
|
188 | if cnx_billing: |
|
189 | cnx_billing.close() |
|
190 | raise falcon.HTTPError(status=falcon.HTTP_404, title='API.NOT_FOUND', |
|
191 | description='API.VIRTUAL_METER_NOT_FOUND') |
|
192 | ||
193 | virtual_meter = dict() |
|
194 | virtual_meter['id'] = row_virtual_meter[0] |
|
195 | virtual_meter['name'] = row_virtual_meter[1] |
|
196 | virtual_meter['cost_center_id'] = row_virtual_meter[2] |
|
197 | virtual_meter['energy_category_id'] = row_virtual_meter[3] |
|
198 | virtual_meter['energy_category_name'] = row_virtual_meter[4] |
|
199 | virtual_meter['unit_of_measure'] = config.currency_unit |
|
200 | virtual_meter['kgce'] = row_virtual_meter[6] |
|
201 | virtual_meter['kgco2e'] = row_virtual_meter[7] |
|
202 | ||
203 | ################################################################################################################ |
|
204 | # Step 3: query base period energy consumption |
|
205 | ################################################################################################################ |
|
206 | query = (" SELECT start_datetime_utc, actual_value " |
|
207 | " FROM tbl_virtual_meter_hourly " |
|
208 | " WHERE virtual_meter_id = %s " |
|
209 | " AND start_datetime_utc >= %s " |
|
210 | " AND start_datetime_utc < %s " |
|
211 | " ORDER BY start_datetime_utc ") |
|
212 | cursor_energy.execute(query, (virtual_meter['id'], base_start_datetime_utc, base_end_datetime_utc)) |
|
213 | rows_virtual_meter_hourly = cursor_energy.fetchall() |
|
214 | ||
215 | rows_virtual_meter_periodically = utilities.aggregate_hourly_data_by_period(rows_virtual_meter_hourly, |
|
216 | base_start_datetime_utc, |
|
217 | base_end_datetime_utc, |
|
218 | period_type) |
|
219 | base = dict() |
|
220 | base['timestamps'] = list() |
|
221 | base['values'] = list() |
|
222 | base['total_in_category'] = Decimal(0.0) |
|
223 | base['total_in_kgce'] = Decimal(0.0) |
|
224 | base['total_in_kgco2e'] = Decimal(0.0) |
|
225 | ||
226 | for row_virtual_meter_periodically in rows_virtual_meter_periodically: |
|
227 | current_datetime_local = row_virtual_meter_periodically[0].replace(tzinfo=timezone.utc) + \ |
|
228 | timedelta(minutes=timezone_offset) |
|
229 | if period_type == 'hourly': |
|
230 | current_datetime = current_datetime_local.isoformat()[0:19] |
|
231 | elif period_type == 'daily': |
|
232 | current_datetime = current_datetime_local.isoformat()[0:10] |
|
233 | elif period_type == 'weekly': |
|
234 | current_datetime = current_datetime_local.isoformat()[0:10] |
|
235 | elif period_type == 'monthly': |
|
236 | current_datetime = current_datetime_local.isoformat()[0:7] |
|
237 | elif period_type == 'yearly': |
|
238 | current_datetime = current_datetime_local.isoformat()[0:4] |
|
239 | ||
240 | actual_value = Decimal(0.0) if row_virtual_meter_periodically[1] is None \ |
|
241 | else row_virtual_meter_periodically[1] |
|
242 | base['timestamps'].append(current_datetime) |
|
243 | base['total_in_kgce'] += actual_value * virtual_meter['kgce'] |
|
244 | base['total_in_kgco2e'] += actual_value * virtual_meter['kgco2e'] |
|
245 | ||
246 | ################################################################################################################ |
|
247 | # Step 4: query base period energy cost |
|
248 | ################################################################################################################ |
|
249 | query = (" SELECT start_datetime_utc, actual_value " |
|
250 | " FROM tbl_virtual_meter_hourly " |
|
251 | " WHERE virtual_meter_id = %s " |
|
252 | " AND start_datetime_utc >= %s " |
|
253 | " AND start_datetime_utc < %s " |
|
254 | " ORDER BY start_datetime_utc ") |
|
255 | cursor_billing.execute(query, (virtual_meter['id'], base_start_datetime_utc, base_end_datetime_utc)) |
|
256 | rows_virtual_meter_hourly = cursor_billing.fetchall() |
|
257 | ||
258 | rows_virtual_meter_periodically = utilities.aggregate_hourly_data_by_period(rows_virtual_meter_hourly, |
|
259 | base_start_datetime_utc, |
|
260 | base_end_datetime_utc, |
|
261 | period_type) |
|
262 | ||
263 | base['values'] = list() |
|
264 | base['total_in_category'] = Decimal(0.0) |
|
265 | ||
266 | for row_virtual_meter_periodically in rows_virtual_meter_periodically: |
|
267 | actual_value = Decimal(0.0) if row_virtual_meter_periodically[1] is None \ |
|
268 | else row_virtual_meter_periodically[1] |
|
269 | base['values'].append(actual_value) |
|
270 | base['total_in_category'] += actual_value |
|
271 | ||
272 | ################################################################################################################ |
|
273 | # Step 5: query reporting period energy consumption |
|
274 | ################################################################################################################ |
|
275 | query = (" SELECT start_datetime_utc, actual_value " |
|
276 | " FROM tbl_virtual_meter_hourly " |
|
277 | " WHERE virtual_meter_id = %s " |
|
278 | " AND start_datetime_utc >= %s " |
|
279 | " AND start_datetime_utc < %s " |
|
280 | " ORDER BY start_datetime_utc ") |
|
281 | cursor_billing.execute(query, (virtual_meter['id'], reporting_start_datetime_utc, reporting_end_datetime_utc)) |
|
282 | rows_virtual_meter_hourly = cursor_billing.fetchall() |
|
283 | ||
284 | rows_virtual_meter_periodically = utilities.aggregate_hourly_data_by_period(rows_virtual_meter_hourly, |
|
285 | reporting_start_datetime_utc, |
|
286 | reporting_end_datetime_utc, |
|
287 | period_type) |
|
288 | reporting = dict() |
|
289 | reporting['timestamps'] = list() |
|
290 | reporting['values'] = list() |
|
291 | reporting['rates'] = list() |
|
292 | reporting['total_in_category'] = Decimal(0.0) |
|
293 | reporting['total_in_kgce'] = Decimal(0.0) |
|
294 | reporting['total_in_kgco2e'] = Decimal(0.0) |
|
295 | ||
296 | for row_virtual_meter_periodically in rows_virtual_meter_periodically: |
|
297 | current_datetime_local = row_virtual_meter_periodically[0].replace(tzinfo=timezone.utc) + \ |
|
298 | timedelta(minutes=timezone_offset) |
|
299 | if period_type == 'hourly': |
|
300 | current_datetime = current_datetime_local.isoformat()[0:19] |
|
301 | elif period_type == 'daily': |
|
302 | current_datetime = current_datetime_local.isoformat()[0:10] |
|
303 | elif period_type == 'weekly': |
|
304 | current_datetime = current_datetime_local.isoformat()[0:10] |
|
305 | elif period_type == 'monthly': |
|
306 | current_datetime = current_datetime_local.isoformat()[0:7] |
|
307 | elif period_type == 'yearly': |
|
308 | current_datetime = current_datetime_local.isoformat()[0:4] |
|
309 | ||
310 | actual_value = Decimal(0.0) if row_virtual_meter_periodically[1] is None \ |
|
311 | else row_virtual_meter_periodically[1] |
|
312 | ||
313 | reporting['timestamps'].append(current_datetime) |
|
314 | reporting['total_in_kgce'] += actual_value * virtual_meter['kgce'] |
|
315 | reporting['total_in_kgco2e'] += actual_value * virtual_meter['kgco2e'] |
|
316 | ||
317 | ################################################################################################################ |
|
318 | # Step 6: query reporting period energy cost |
|
319 | ################################################################################################################ |
|
320 | query = (" SELECT start_datetime_utc, actual_value " |
|
321 | " FROM tbl_virtual_meter_hourly " |
|
322 | " WHERE virtual_meter_id = %s " |
|
323 | " AND start_datetime_utc >= %s " |
|
324 | " AND start_datetime_utc < %s " |
|
325 | " ORDER BY start_datetime_utc ") |
|
326 | cursor_billing.execute(query, (virtual_meter['id'], reporting_start_datetime_utc, reporting_end_datetime_utc)) |
|
327 | rows_virtual_meter_hourly = cursor_billing.fetchall() |
|
328 | ||
329 | rows_virtual_meter_periodically = utilities.aggregate_hourly_data_by_period(rows_virtual_meter_hourly, |
|
330 | reporting_start_datetime_utc, |
|
331 | reporting_end_datetime_utc, |
|
332 | period_type) |
|
333 | ||
334 | for row_virtual_meter_periodically in rows_virtual_meter_periodically: |
|
335 | actual_value = Decimal(0.0) if row_virtual_meter_periodically[1] is None \ |
|
336 | else row_virtual_meter_periodically[1] |
|
337 | ||
338 | reporting['values'].append(actual_value) |
|
339 | reporting['total_in_category'] += actual_value |
|
340 | ||
341 | for index, value in enumerate(reporting['values']): |
|
342 | if index < len(base['values']) and base['values'][index] != 0 and value != 0: |
|
343 | reporting['rates'].append((value - base['values'][index]) / base['values'][index]) |
|
344 | else: |
|
345 | reporting['rates'].append(None) |
|
346 | ||
347 | ################################################################################################################ |
|
348 | # Step 7: query tariff data |
|
349 | ################################################################################################################ |
|
350 | parameters_data = dict() |
|
351 | parameters_data['names'] = list() |
|
352 | parameters_data['timestamps'] = list() |
|
353 | parameters_data['values'] = list() |
|
354 | if config.is_tariff_appended and is_quick_mode: |
|
355 | tariff_dict = utilities.get_energy_category_tariffs(virtual_meter['cost_center_id'], |
|
356 | virtual_meter['energy_category_id'], |
|
357 | reporting_start_datetime_utc, |
|
358 | reporting_end_datetime_utc) |
|
359 | tariff_timestamp_list = list() |
|
360 | tariff_value_list = list() |
|
361 | for k, v in tariff_dict.items(): |
|
362 | # convert k from utc to local |
|
363 | k = k + timedelta(minutes=timezone_offset) |
|
364 | tariff_timestamp_list.append(k.isoformat()[0:19]) |
|
365 | tariff_value_list.append(v) |
|
366 | ||
367 | parameters_data['names'].append(_('Tariff') + '-' + virtual_meter['energy_category_name']) |
|
368 | parameters_data['timestamps'].append(tariff_timestamp_list) |
|
369 | parameters_data['values'].append(tariff_value_list) |
|
370 | ||
371 | ################################################################################################################ |
|
372 | # Step 8: construct the report |
|
373 | ################################################################################################################ |
|
374 | if cursor_system: |
|
375 | cursor_system.close() |
|
376 | if cnx_system: |
|
377 | cnx_system.close() |
|
378 | ||
379 | if cursor_energy: |
|
380 | cursor_energy.close() |
|
381 | if cnx_energy: |
|
382 | cnx_energy.close() |
|
383 | ||
384 | if cursor_billing: |
|
385 | cursor_billing.close() |
|
386 | if cnx_billing: |
|
387 | cnx_billing.close() |
|
388 | ||
389 | result = { |
|
390 | "virtual_meter": { |
|
391 | "cost_center_id": virtual_meter['cost_center_id'], |
|
392 | "energy_category_id": virtual_meter['energy_category_id'], |
|
393 | "energy_category_name": virtual_meter['energy_category_name'], |
|
394 | "unit_of_measure": config.currency_unit, |
|
395 | "kgce": virtual_meter['kgce'], |
|
396 | "kgco2e": virtual_meter['kgco2e'], |
|
397 | }, |
|
398 | "base_period": { |
|
399 | "total_in_category": base['total_in_category'], |
|
400 | "total_in_kgce": base['total_in_kgce'], |
|
401 | "total_in_kgco2e": base['total_in_kgco2e'], |
|
402 | "timestamps": base['timestamps'], |
|
403 | "values": base['values'], |
|
404 | }, |
|
405 | "reporting_period": { |
|
406 | "increment_rate": |
|
407 | (reporting['total_in_category'] - base['total_in_category']) / base['total_in_category'] |
|
408 | if base['total_in_category'] != Decimal(0.0) else None, |
|
409 | "total_in_category": reporting['total_in_category'], |
|
410 | "total_in_kgce": reporting['total_in_kgce'], |
|
411 | "total_in_kgco2e": reporting['total_in_kgco2e'], |
|
412 | "timestamps": reporting['timestamps'], |
|
413 | "values": reporting['values'], |
|
414 | "rates": reporting['rates'], |
|
415 | }, |
|
416 | "parameters": { |
|
417 | "names": parameters_data['names'], |
|
418 | "timestamps": parameters_data['timestamps'], |
|
419 | "values": parameters_data['values'] |
|
420 | }, |
|
421 | } |
|
422 | ||
423 | # export result to Excel file and then encode the file to base64 string |
|
424 | if not is_quick_mode: |
|
425 | result['excel_bytes_base64'] = \ |
|
426 | excelexporters.virtualmetercost.export(result, |
|
427 | virtual_meter['name'], |
|
428 | base_period_start_datetime_local, |
|
429 | base_period_end_datetime_local, |
|
430 | reporting_period_start_datetime_local, |
|
431 | reporting_period_end_datetime_local, |
|
432 | period_type, |
|
433 | language) |
|
434 | ||
435 | resp.text = json.dumps(result) |
|
436 |
@@ 12-431 (lines=420) @@ | ||
9 | from core.useractivity import access_control, api_key_control |
|
10 | ||
11 | ||
12 | class Reporting: |
|
13 | def __init__(self): |
|
14 | """"Initializes Reporting""" |
|
15 | pass |
|
16 | ||
17 | @staticmethod |
|
18 | def on_options(req, resp): |
|
19 | _ = req |
|
20 | resp.status = falcon.HTTP_200 |
|
21 | ||
22 | #################################################################################################################### |
|
23 | # PROCEDURES |
|
24 | # Step 1: valid parameters |
|
25 | # Step 2: query the offline meter and energy category |
|
26 | # Step 3: query base period energy consumption |
|
27 | # Step 4: query base period energy cost |
|
28 | # Step 5: query reporting period energy consumption |
|
29 | # Step 6: query reporting period energy cost |
|
30 | # Step 7: query tariff data |
|
31 | # Step 8: construct the report |
|
32 | #################################################################################################################### |
|
33 | @staticmethod |
|
34 | def on_get(req, resp): |
|
35 | if 'API-KEY' not in req.headers or \ |
|
36 | not isinstance(req.headers['API-KEY'], str) or \ |
|
37 | len(str.strip(req.headers['API-KEY'])) == 0: |
|
38 | access_control(req) |
|
39 | else: |
|
40 | api_key_control(req) |
|
41 | print(req.params) |
|
42 | offline_meter_id = req.params.get('offlinemeterid') |
|
43 | period_type = req.params.get('periodtype') |
|
44 | base_period_start_datetime_local = req.params.get('baseperiodstartdatetime') |
|
45 | base_period_end_datetime_local = req.params.get('baseperiodenddatetime') |
|
46 | reporting_period_start_datetime_local = req.params.get('reportingperiodstartdatetime') |
|
47 | reporting_period_end_datetime_local = req.params.get('reportingperiodenddatetime') |
|
48 | language = req.params.get('language') |
|
49 | quick_mode = req.params.get('quickmode') |
|
50 | ||
51 | ################################################################################################################ |
|
52 | # Step 1: valid parameters |
|
53 | ################################################################################################################ |
|
54 | if offline_meter_id is None: |
|
55 | raise falcon.HTTPError(status=falcon.HTTP_400, |
|
56 | title='API.BAD_REQUEST', |
|
57 | description='API.INVALID_OFFLINE_METER_ID') |
|
58 | else: |
|
59 | offline_meter_id = str.strip(offline_meter_id) |
|
60 | if not offline_meter_id.isdigit() or int(offline_meter_id) <= 0: |
|
61 | raise falcon.HTTPError(status=falcon.HTTP_400, |
|
62 | title='API.BAD_REQUEST', |
|
63 | description='API.INVALID_OFFLINE_METER_ID') |
|
64 | ||
65 | if period_type is None: |
|
66 | raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST', |
|
67 | description='API.INVALID_PERIOD_TYPE') |
|
68 | else: |
|
69 | period_type = str.strip(period_type) |
|
70 | if period_type not in ['hourly', 'daily', 'weekly', 'monthly', 'yearly']: |
|
71 | raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST', |
|
72 | description='API.INVALID_PERIOD_TYPE') |
|
73 | ||
74 | timezone_offset = int(config.utc_offset[1:3]) * 60 + int(config.utc_offset[4:6]) |
|
75 | if config.utc_offset[0] == '-': |
|
76 | timezone_offset = -timezone_offset |
|
77 | ||
78 | base_start_datetime_utc = None |
|
79 | if base_period_end_datetime_local is not None and len(str.strip(base_period_end_datetime_local)) > 0: |
|
80 | base_period_start_datetime_local = str.strip(base_period_start_datetime_local) |
|
81 | try: |
|
82 | base_start_datetime_utc = datetime.strptime(base_period_start_datetime_local, '%Y-%m-%dT%H:%M:%S') |
|
83 | except ValueError: |
|
84 | raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST', |
|
85 | description="API.INVALID_BASE_PERIOD_START_DATETIME") |
|
86 | base_start_datetime_utc = \ |
|
87 | base_start_datetime_utc.replace(tzinfo=timezone.utc) - timedelta(minutes=timezone_offset) |
|
88 | # nomalize the start datetime |
|
89 | if config.minutes_to_count == 30 and base_start_datetime_utc.minute >= 30: |
|
90 | base_start_datetime_utc = base_start_datetime_utc.replace(minute=30, second=0, microsecond=0) |
|
91 | else: |
|
92 | base_start_datetime_utc = base_start_datetime_utc.replace(minute=0, second=0, microsecond=0) |
|
93 | ||
94 | base_end_datetime_utc = None |
|
95 | if base_period_end_datetime_local is not None and len(str.strip(base_period_end_datetime_local)) > 0: |
|
96 | base_period_end_datetime_local = str.strip(base_period_end_datetime_local) |
|
97 | try: |
|
98 | base_end_datetime_utc = datetime.strptime(base_period_end_datetime_local, '%Y-%m-%dT%H:%M:%S') |
|
99 | except ValueError: |
|
100 | raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST', |
|
101 | description="API.INVALID_BASE_PERIOD_END_DATETIME") |
|
102 | base_end_datetime_utc = \ |
|
103 | base_end_datetime_utc.replace(tzinfo=timezone.utc) - timedelta(minutes=timezone_offset) |
|
104 | ||
105 | if base_start_datetime_utc is not None and base_end_datetime_utc is not None and \ |
|
106 | base_start_datetime_utc >= base_end_datetime_utc: |
|
107 | raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST', |
|
108 | description='API.INVALID_BASE_PERIOD_END_DATETIME') |
|
109 | ||
110 | if reporting_period_start_datetime_local is None: |
|
111 | raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST', |
|
112 | description="API.INVALID_REPORTING_PERIOD_START_DATETIME") |
|
113 | else: |
|
114 | reporting_period_start_datetime_local = str.strip(reporting_period_start_datetime_local) |
|
115 | try: |
|
116 | reporting_start_datetime_utc = datetime.strptime(reporting_period_start_datetime_local, |
|
117 | '%Y-%m-%dT%H:%M:%S') |
|
118 | except ValueError: |
|
119 | raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST', |
|
120 | description="API.INVALID_REPORTING_PERIOD_START_DATETIME") |
|
121 | reporting_start_datetime_utc = \ |
|
122 | reporting_start_datetime_utc.replace(tzinfo=timezone.utc) - timedelta(minutes=timezone_offset) |
|
123 | # nomalize the start datetime |
|
124 | if config.minutes_to_count == 30 and reporting_start_datetime_utc.minute >= 30: |
|
125 | reporting_start_datetime_utc = reporting_start_datetime_utc.replace(minute=30, second=0, microsecond=0) |
|
126 | else: |
|
127 | reporting_start_datetime_utc = reporting_start_datetime_utc.replace(minute=0, second=0, microsecond=0) |
|
128 | ||
129 | if reporting_period_end_datetime_local is None: |
|
130 | raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST', |
|
131 | description="API.INVALID_REPORTING_PERIOD_END_DATETIME") |
|
132 | else: |
|
133 | reporting_period_end_datetime_local = str.strip(reporting_period_end_datetime_local) |
|
134 | try: |
|
135 | reporting_end_datetime_utc = datetime.strptime(reporting_period_end_datetime_local, |
|
136 | '%Y-%m-%dT%H:%M:%S') |
|
137 | except ValueError: |
|
138 | raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST', |
|
139 | description="API.INVALID_REPORTING_PERIOD_END_DATETIME") |
|
140 | reporting_end_datetime_utc = reporting_end_datetime_utc.replace(tzinfo=timezone.utc) - \ |
|
141 | timedelta(minutes=timezone_offset) |
|
142 | ||
143 | if reporting_start_datetime_utc >= reporting_end_datetime_utc: |
|
144 | raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST', |
|
145 | description='API.INVALID_REPORTING_PERIOD_END_DATETIME') |
|
146 | ||
147 | # if turn quick mode on, do not return parameters data and excel file |
|
148 | is_quick_mode = False |
|
149 | if quick_mode is not None and \ |
|
150 | len(str.strip(quick_mode)) > 0 and \ |
|
151 | str.lower(str.strip(quick_mode)) in ('true', 't', 'on', 'yes', 'y'): |
|
152 | is_quick_mode = True |
|
153 | ||
154 | trans = utilities.get_translation(language) |
|
155 | trans.install() |
|
156 | _ = trans.gettext |
|
157 | ||
158 | ################################################################################################################ |
|
159 | # Step 2: query the offline meter and energy category |
|
160 | ################################################################################################################ |
|
161 | cnx_system = mysql.connector.connect(**config.myems_system_db) |
|
162 | cursor_system = cnx_system.cursor() |
|
163 | ||
164 | cnx_energy = mysql.connector.connect(**config.myems_energy_db) |
|
165 | cursor_energy = cnx_energy.cursor() |
|
166 | ||
167 | cnx_billing = mysql.connector.connect(**config.myems_billing_db) |
|
168 | cursor_billing = cnx_billing.cursor() |
|
169 | ||
170 | cursor_system.execute(" SELECT m.id, m.name, m.cost_center_id, m.energy_category_id, " |
|
171 | " ec.name, ec.unit_of_measure, ec.kgce, ec.kgco2e " |
|
172 | " FROM tbl_offline_meters m, tbl_energy_categories ec " |
|
173 | " WHERE m.id = %s AND m.energy_category_id = ec.id ", (offline_meter_id,)) |
|
174 | row_offline_meter = cursor_system.fetchone() |
|
175 | if row_offline_meter is None: |
|
176 | if cursor_system: |
|
177 | cursor_system.close() |
|
178 | if cnx_system: |
|
179 | cnx_system.close() |
|
180 | ||
181 | if cursor_energy: |
|
182 | cursor_energy.close() |
|
183 | if cnx_energy: |
|
184 | cnx_energy.close() |
|
185 | ||
186 | if cursor_billing: |
|
187 | cursor_billing.close() |
|
188 | if cnx_billing: |
|
189 | cnx_billing.close() |
|
190 | ||
191 | raise falcon.HTTPError(status=falcon.HTTP_404, title='API.NOT_FOUND', |
|
192 | description='API.OFFLINE_METER_NOT_FOUND') |
|
193 | ||
194 | offline_meter = dict() |
|
195 | offline_meter['id'] = row_offline_meter[0] |
|
196 | offline_meter['name'] = row_offline_meter[1] |
|
197 | offline_meter['cost_center_id'] = row_offline_meter[2] |
|
198 | offline_meter['energy_category_id'] = row_offline_meter[3] |
|
199 | offline_meter['energy_category_name'] = row_offline_meter[4] |
|
200 | offline_meter['unit_of_measure'] = config.currency_unit |
|
201 | offline_meter['kgce'] = row_offline_meter[6] |
|
202 | offline_meter['kgco2e'] = row_offline_meter[7] |
|
203 | ||
204 | ################################################################################################################ |
|
205 | # Step 3: query base period energy consumption |
|
206 | ################################################################################################################ |
|
207 | query = (" SELECT start_datetime_utc, actual_value " |
|
208 | " FROM tbl_offline_meter_hourly " |
|
209 | " WHERE offline_meter_id = %s " |
|
210 | " AND start_datetime_utc >= %s " |
|
211 | " AND start_datetime_utc < %s " |
|
212 | " ORDER BY start_datetime_utc ") |
|
213 | cursor_energy.execute(query, (offline_meter['id'], base_start_datetime_utc, base_end_datetime_utc)) |
|
214 | rows_offline_meter_hourly = cursor_energy.fetchall() |
|
215 | ||
216 | rows_offline_meter_periodically = utilities.aggregate_hourly_data_by_period(rows_offline_meter_hourly, |
|
217 | base_start_datetime_utc, |
|
218 | base_end_datetime_utc, |
|
219 | period_type) |
|
220 | base = dict() |
|
221 | base['timestamps'] = list() |
|
222 | base['values'] = list() |
|
223 | base['total_in_category'] = Decimal(0.0) |
|
224 | base['total_in_kgce'] = Decimal(0.0) |
|
225 | base['total_in_kgco2e'] = Decimal(0.0) |
|
226 | ||
227 | for row_offline_meter_periodically in rows_offline_meter_periodically: |
|
228 | current_datetime_local = row_offline_meter_periodically[0].replace(tzinfo=timezone.utc) + \ |
|
229 | timedelta(minutes=timezone_offset) |
|
230 | if period_type == 'hourly': |
|
231 | current_datetime = current_datetime_local.isoformat()[0:19] |
|
232 | elif period_type == 'daily': |
|
233 | current_datetime = current_datetime_local.isoformat()[0:10] |
|
234 | elif period_type == 'weekly': |
|
235 | current_datetime = current_datetime_local.isoformat()[0:10] |
|
236 | elif period_type == 'monthly': |
|
237 | current_datetime = current_datetime_local.isoformat()[0:7] |
|
238 | elif period_type == 'yearly': |
|
239 | current_datetime = current_datetime_local.isoformat()[0:4] |
|
240 | ||
241 | actual_value = Decimal(0.0) if row_offline_meter_periodically[1] is None \ |
|
242 | else row_offline_meter_periodically[1] |
|
243 | base['timestamps'].append(current_datetime) |
|
244 | base['total_in_kgce'] += actual_value * offline_meter['kgce'] |
|
245 | base['total_in_kgco2e'] += actual_value * offline_meter['kgco2e'] |
|
246 | ||
247 | ################################################################################################################ |
|
248 | # Step 4: query base period energy cost |
|
249 | ################################################################################################################ |
|
250 | query = (" SELECT start_datetime_utc, actual_value " |
|
251 | " FROM tbl_offline_meter_hourly " |
|
252 | " WHERE offline_meter_id = %s " |
|
253 | " AND start_datetime_utc >= %s " |
|
254 | " AND start_datetime_utc < %s " |
|
255 | " ORDER BY start_datetime_utc ") |
|
256 | cursor_billing.execute(query, (offline_meter['id'], base_start_datetime_utc, base_end_datetime_utc)) |
|
257 | rows_offline_meter_hourly = cursor_billing.fetchall() |
|
258 | ||
259 | rows_offline_meter_periodically = utilities.aggregate_hourly_data_by_period(rows_offline_meter_hourly, |
|
260 | base_start_datetime_utc, |
|
261 | base_end_datetime_utc, |
|
262 | period_type) |
|
263 | ||
264 | base['values'] = list() |
|
265 | base['total_in_category'] = Decimal(0.0) |
|
266 | ||
267 | for row_offline_meter_periodically in rows_offline_meter_periodically: |
|
268 | actual_value = Decimal(0.0) if row_offline_meter_periodically[1] is None \ |
|
269 | else row_offline_meter_periodically[1] |
|
270 | base['values'].append(actual_value) |
|
271 | base['total_in_category'] += actual_value |
|
272 | ||
273 | ################################################################################################################ |
|
274 | # Step 5: query reporting period energy consumption |
|
275 | ################################################################################################################ |
|
276 | query = (" SELECT start_datetime_utc, actual_value " |
|
277 | " FROM tbl_offline_meter_hourly " |
|
278 | " WHERE offline_meter_id = %s " |
|
279 | " AND start_datetime_utc >= %s " |
|
280 | " AND start_datetime_utc < %s " |
|
281 | " ORDER BY start_datetime_utc ") |
|
282 | cursor_energy.execute(query, (offline_meter['id'], reporting_start_datetime_utc, reporting_end_datetime_utc)) |
|
283 | rows_offline_meter_hourly = cursor_energy.fetchall() |
|
284 | ||
285 | rows_offline_meter_periodically = utilities.aggregate_hourly_data_by_period(rows_offline_meter_hourly, |
|
286 | reporting_start_datetime_utc, |
|
287 | reporting_end_datetime_utc, |
|
288 | period_type) |
|
289 | reporting = dict() |
|
290 | reporting['timestamps'] = list() |
|
291 | reporting['values'] = list() |
|
292 | reporting['rates'] = list() |
|
293 | reporting['total_in_category'] = Decimal(0.0) |
|
294 | reporting['total_in_kgce'] = Decimal(0.0) |
|
295 | reporting['total_in_kgco2e'] = Decimal(0.0) |
|
296 | ||
297 | for row_offline_meter_periodically in rows_offline_meter_periodically: |
|
298 | current_datetime_local = row_offline_meter_periodically[0].replace(tzinfo=timezone.utc) + \ |
|
299 | timedelta(minutes=timezone_offset) |
|
300 | if period_type == 'hourly': |
|
301 | current_datetime = current_datetime_local.isoformat()[0:19] |
|
302 | elif period_type == 'daily': |
|
303 | current_datetime = current_datetime_local.isoformat()[0:10] |
|
304 | elif period_type == 'weekly': |
|
305 | current_datetime = current_datetime_local.isoformat()[0:10] |
|
306 | elif period_type == 'monthly': |
|
307 | current_datetime = current_datetime_local.isoformat()[0:7] |
|
308 | elif period_type == 'yearly': |
|
309 | current_datetime = current_datetime_local.isoformat()[0:4] |
|
310 | ||
311 | actual_value = Decimal(0.0) if row_offline_meter_periodically[1] is None \ |
|
312 | else row_offline_meter_periodically[1] |
|
313 | ||
314 | reporting['timestamps'].append(current_datetime) |
|
315 | reporting['total_in_kgce'] += actual_value * offline_meter['kgce'] |
|
316 | reporting['total_in_kgco2e'] += actual_value * offline_meter['kgco2e'] |
|
317 | ||
318 | ################################################################################################################ |
|
319 | # Step 6: query reporting period energy cost |
|
320 | ################################################################################################################ |
|
321 | query = (" SELECT start_datetime_utc, actual_value " |
|
322 | " FROM tbl_offline_meter_hourly " |
|
323 | " WHERE offline_meter_id = %s " |
|
324 | " AND start_datetime_utc >= %s " |
|
325 | " AND start_datetime_utc < %s " |
|
326 | " ORDER BY start_datetime_utc ") |
|
327 | cursor_billing.execute(query, (offline_meter['id'], reporting_start_datetime_utc, reporting_end_datetime_utc)) |
|
328 | rows_offline_meter_hourly = cursor_billing.fetchall() |
|
329 | ||
330 | rows_offline_meter_periodically = utilities.aggregate_hourly_data_by_period(rows_offline_meter_hourly, |
|
331 | reporting_start_datetime_utc, |
|
332 | reporting_end_datetime_utc, |
|
333 | period_type) |
|
334 | ||
335 | for row_offline_meter_periodically in rows_offline_meter_periodically: |
|
336 | actual_value = Decimal(0.0) if row_offline_meter_periodically[1] is None \ |
|
337 | else row_offline_meter_periodically[1] |
|
338 | ||
339 | reporting['values'].append(actual_value) |
|
340 | reporting['total_in_category'] += actual_value |
|
341 | ||
342 | for index, value in enumerate(reporting['values']): |
|
343 | if index < len(base['values']) and base['values'][index] != 0 and value != 0: |
|
344 | reporting['rates'].append((value - base['values'][index]) / base['values'][index]) |
|
345 | else: |
|
346 | reporting['rates'].append(None) |
|
347 | ||
348 | ################################################################################################################ |
|
349 | # Step 7: query tariff data |
|
350 | ################################################################################################################ |
|
351 | parameters_data = dict() |
|
352 | parameters_data['names'] = list() |
|
353 | parameters_data['timestamps'] = list() |
|
354 | parameters_data['values'] = list() |
|
355 | if config.is_tariff_appended and not is_quick_mode: |
|
356 | tariff_dict = utilities.get_energy_category_tariffs(offline_meter['cost_center_id'], |
|
357 | offline_meter['energy_category_id'], |
|
358 | reporting_start_datetime_utc, |
|
359 | reporting_end_datetime_utc) |
|
360 | tariff_timestamp_list = list() |
|
361 | tariff_value_list = list() |
|
362 | for k, v in tariff_dict.items(): |
|
363 | # convert k from utc to local |
|
364 | k = k + timedelta(minutes=timezone_offset) |
|
365 | tariff_timestamp_list.append(k.isoformat()[0:19]) |
|
366 | tariff_value_list.append(v) |
|
367 | ||
368 | parameters_data['names'].append(_('Tariff') + '-' + offline_meter['energy_category_name']) |
|
369 | parameters_data['timestamps'].append(tariff_timestamp_list) |
|
370 | parameters_data['values'].append(tariff_value_list) |
|
371 | ||
372 | ################################################################################################################ |
|
373 | # Step 8: construct the report |
|
374 | ################################################################################################################ |
|
375 | if cursor_system: |
|
376 | cursor_system.close() |
|
377 | if cnx_system: |
|
378 | cnx_system.close() |
|
379 | ||
380 | if cursor_energy: |
|
381 | cursor_energy.close() |
|
382 | if cnx_energy: |
|
383 | cnx_energy.close() |
|
384 | ||
385 | if cursor_billing: |
|
386 | cursor_billing.close() |
|
387 | if cnx_billing: |
|
388 | cnx_billing.close() |
|
389 | ||
390 | result = {"offline_meter": { |
|
391 | "cost_center_id": offline_meter['cost_center_id'], |
|
392 | "energy_category_id": offline_meter['energy_category_id'], |
|
393 | "energy_category_name": offline_meter['energy_category_name'], |
|
394 | "unit_of_measure": config.currency_unit, |
|
395 | "kgce": offline_meter['kgce'], |
|
396 | "kgco2e": offline_meter['kgco2e'], |
|
397 | }, "base_period": { |
|
398 | "total_in_category": base['total_in_category'], |
|
399 | "total_in_kgce": base['total_in_kgce'], |
|
400 | "total_in_kgco2e": base['total_in_kgco2e'], |
|
401 | "timestamps": base['timestamps'], |
|
402 | "values": base['values'], |
|
403 | }, "reporting_period": { |
|
404 | "increment_rate": |
|
405 | (reporting['total_in_category'] - base['total_in_category']) / base['total_in_category'] |
|
406 | if base['total_in_category'] != Decimal(0.0) else None, |
|
407 | "total_in_category": reporting['total_in_category'], |
|
408 | "total_in_kgce": reporting['total_in_kgce'], |
|
409 | "total_in_kgco2e": reporting['total_in_kgco2e'], |
|
410 | "timestamps": reporting['timestamps'], |
|
411 | "values": reporting['values'], |
|
412 | "rates": reporting['rates'], |
|
413 | }, "parameters": { |
|
414 | "names": parameters_data['names'], |
|
415 | "timestamps": parameters_data['timestamps'], |
|
416 | "values": parameters_data['values'] |
|
417 | }, 'excel_bytes_base64': None} |
|
418 | ||
419 | # export result to Excel file and then encode the file to base64 string |
|
420 | if not is_quick_mode: |
|
421 | result['excel_bytes_base64'] = \ |
|
422 | excelexporters.offlinemetercost.export(result, |
|
423 | offline_meter['name'], |
|
424 | base_period_start_datetime_local, |
|
425 | base_period_end_datetime_local, |
|
426 | reporting_period_start_datetime_local, |
|
427 | reporting_period_end_datetime_local, |
|
428 | period_type, |
|
429 | language) |
|
430 | ||
431 | resp.text = json.dumps(result) |
|
432 |
@@ 12-431 (lines=420) @@ | ||
9 | from core.useractivity import access_control, api_key_control |
|
10 | ||
11 | ||
12 | class Reporting: |
|
13 | def __init__(self): |
|
14 | """"Initializes Reporting""" |
|
15 | pass |
|
16 | ||
17 | @staticmethod |
|
18 | def on_options(req, resp): |
|
19 | _ = req |
|
20 | resp.status = falcon.HTTP_200 |
|
21 | ||
22 | #################################################################################################################### |
|
23 | # PROCEDURES |
|
24 | # Step 1: valid parameters |
|
25 | # Step 2: query the offline meter and energy category |
|
26 | # Step 3: query base period energy consumption |
|
27 | # Step 4: query base period energy carbon dioxide emissions |
|
28 | # Step 5: query reporting period energy consumption |
|
29 | # Step 6: query reporting period energy carbon dioxide emissions |
|
30 | # Step 7: query tariff data |
|
31 | # Step 8: construct the report |
|
32 | #################################################################################################################### |
|
33 | @staticmethod |
|
34 | def on_get(req, resp): |
|
35 | if 'API-KEY' not in req.headers or \ |
|
36 | not isinstance(req.headers['API-KEY'], str) or \ |
|
37 | len(str.strip(req.headers['API-KEY'])) == 0: |
|
38 | access_control(req) |
|
39 | else: |
|
40 | api_key_control(req) |
|
41 | print(req.params) |
|
42 | offline_meter_id = req.params.get('offlinemeterid') |
|
43 | period_type = req.params.get('periodtype') |
|
44 | base_period_start_datetime_local = req.params.get('baseperiodstartdatetime') |
|
45 | base_period_end_datetime_local = req.params.get('baseperiodenddatetime') |
|
46 | reporting_period_start_datetime_local = req.params.get('reportingperiodstartdatetime') |
|
47 | reporting_period_end_datetime_local = req.params.get('reportingperiodenddatetime') |
|
48 | language = req.params.get('language') |
|
49 | quick_mode = req.params.get('quickmode') |
|
50 | ||
51 | ################################################################################################################ |
|
52 | # Step 1: valid parameters |
|
53 | ################################################################################################################ |
|
54 | if offline_meter_id is None: |
|
55 | raise falcon.HTTPError(status=falcon.HTTP_400, |
|
56 | title='API.BAD_REQUEST', |
|
57 | description='API.INVALID_OFFLINE_METER_ID') |
|
58 | else: |
|
59 | offline_meter_id = str.strip(offline_meter_id) |
|
60 | if not offline_meter_id.isdigit() or int(offline_meter_id) <= 0: |
|
61 | raise falcon.HTTPError(status=falcon.HTTP_400, |
|
62 | title='API.BAD_REQUEST', |
|
63 | description='API.INVALID_OFFLINE_METER_ID') |
|
64 | ||
65 | if period_type is None: |
|
66 | raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST', |
|
67 | description='API.INVALID_PERIOD_TYPE') |
|
68 | else: |
|
69 | period_type = str.strip(period_type) |
|
70 | if period_type not in ['hourly', 'daily', 'weekly', 'monthly', 'yearly']: |
|
71 | raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST', |
|
72 | description='API.INVALID_PERIOD_TYPE') |
|
73 | ||
74 | timezone_offset = int(config.utc_offset[1:3]) * 60 + int(config.utc_offset[4:6]) |
|
75 | if config.utc_offset[0] == '-': |
|
76 | timezone_offset = -timezone_offset |
|
77 | ||
78 | base_start_datetime_utc = None |
|
79 | if base_period_start_datetime_local is not None and len(str.strip(base_period_start_datetime_local)) > 0: |
|
80 | base_period_start_datetime_local = str.strip(base_period_start_datetime_local) |
|
81 | try: |
|
82 | base_start_datetime_utc = datetime.strptime(base_period_start_datetime_local, '%Y-%m-%dT%H:%M:%S') |
|
83 | except ValueError: |
|
84 | raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST', |
|
85 | description="API.INVALID_BASE_PERIOD_START_DATETIME") |
|
86 | base_start_datetime_utc = \ |
|
87 | base_start_datetime_utc.replace(tzinfo=timezone.utc) - timedelta(minutes=timezone_offset) |
|
88 | # nomalize the start datetime |
|
89 | if config.minutes_to_count == 30 and base_start_datetime_utc.minute >= 30: |
|
90 | base_start_datetime_utc = base_start_datetime_utc.replace(minute=30, second=0, microsecond=0) |
|
91 | else: |
|
92 | base_start_datetime_utc = base_start_datetime_utc.replace(minute=0, second=0, microsecond=0) |
|
93 | ||
94 | base_end_datetime_utc = None |
|
95 | if base_period_end_datetime_local is not None and len(str.strip(base_period_end_datetime_local)) > 0: |
|
96 | base_period_end_datetime_local = str.strip(base_period_end_datetime_local) |
|
97 | try: |
|
98 | base_end_datetime_utc = datetime.strptime(base_period_end_datetime_local, '%Y-%m-%dT%H:%M:%S') |
|
99 | except ValueError: |
|
100 | raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST', |
|
101 | description="API.INVALID_BASE_PERIOD_END_DATETIME") |
|
102 | base_end_datetime_utc = \ |
|
103 | base_end_datetime_utc.replace(tzinfo=timezone.utc) - timedelta(minutes=timezone_offset) |
|
104 | ||
105 | if base_start_datetime_utc is not None and base_end_datetime_utc is not None and \ |
|
106 | base_start_datetime_utc >= base_end_datetime_utc: |
|
107 | raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST', |
|
108 | description='API.INVALID_BASE_PERIOD_END_DATETIME') |
|
109 | ||
110 | if reporting_period_start_datetime_local is None: |
|
111 | raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST', |
|
112 | description="API.INVALID_REPORTING_PERIOD_START_DATETIME") |
|
113 | else: |
|
114 | reporting_period_start_datetime_local = str.strip(reporting_period_start_datetime_local) |
|
115 | try: |
|
116 | reporting_start_datetime_utc = datetime.strptime(reporting_period_start_datetime_local, |
|
117 | '%Y-%m-%dT%H:%M:%S') |
|
118 | except ValueError: |
|
119 | raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST', |
|
120 | description="API.INVALID_REPORTING_PERIOD_START_DATETIME") |
|
121 | reporting_start_datetime_utc = \ |
|
122 | reporting_start_datetime_utc.replace(tzinfo=timezone.utc) - timedelta(minutes=timezone_offset) |
|
123 | # nomalize the start datetime |
|
124 | if config.minutes_to_count == 30 and reporting_start_datetime_utc.minute >= 30: |
|
125 | reporting_start_datetime_utc = reporting_start_datetime_utc.replace(minute=30, second=0, microsecond=0) |
|
126 | else: |
|
127 | reporting_start_datetime_utc = reporting_start_datetime_utc.replace(minute=0, second=0, microsecond=0) |
|
128 | ||
129 | if reporting_period_end_datetime_local is None: |
|
130 | raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST', |
|
131 | description="API.INVALID_REPORTING_PERIOD_END_DATETIME") |
|
132 | else: |
|
133 | reporting_period_end_datetime_local = str.strip(reporting_period_end_datetime_local) |
|
134 | try: |
|
135 | reporting_end_datetime_utc = datetime.strptime(reporting_period_end_datetime_local, |
|
136 | '%Y-%m-%dT%H:%M:%S') |
|
137 | except ValueError: |
|
138 | raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST', |
|
139 | description="API.INVALID_REPORTING_PERIOD_END_DATETIME") |
|
140 | reporting_end_datetime_utc = reporting_end_datetime_utc.replace(tzinfo=timezone.utc) - \ |
|
141 | timedelta(minutes=timezone_offset) |
|
142 | ||
143 | if reporting_start_datetime_utc >= reporting_end_datetime_utc: |
|
144 | raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST', |
|
145 | description='API.INVALID_REPORTING_PERIOD_END_DATETIME') |
|
146 | ||
147 | # if turn quick mode on, do not return parameters data and excel file |
|
148 | is_quick_mode = False |
|
149 | if quick_mode is not None and \ |
|
150 | len(str.strip(quick_mode)) > 0 and \ |
|
151 | str.lower(str.strip(quick_mode)) in ('true', 't', 'on', 'yes', 'y'): |
|
152 | is_quick_mode = True |
|
153 | ||
154 | trans = utilities.get_translation(language) |
|
155 | trans.install() |
|
156 | _ = trans.gettext |
|
157 | ||
158 | ################################################################################################################ |
|
159 | # Step 2: query the offline meter and energy category |
|
160 | ################################################################################################################ |
|
161 | cnx_system = mysql.connector.connect(**config.myems_system_db) |
|
162 | cursor_system = cnx_system.cursor() |
|
163 | ||
164 | cnx_energy = mysql.connector.connect(**config.myems_energy_db) |
|
165 | cursor_energy = cnx_energy.cursor() |
|
166 | ||
167 | cnx_carbon = mysql.connector.connect(**config.myems_carbon_db) |
|
168 | cursor_carbon = cnx_carbon.cursor() |
|
169 | ||
170 | cursor_system.execute(" SELECT m.id, m.name, m.cost_center_id, m.energy_category_id, " |
|
171 | " ec.name, ec.unit_of_measure, ec.kgce, ec.kgco2e " |
|
172 | " FROM tbl_offline_meters m, tbl_energy_categories ec " |
|
173 | " WHERE m.id = %s AND m.energy_category_id = ec.id ", (offline_meter_id,)) |
|
174 | row_offline_meter = cursor_system.fetchone() |
|
175 | if row_offline_meter is None: |
|
176 | if cursor_system: |
|
177 | cursor_system.close() |
|
178 | if cnx_system: |
|
179 | cnx_system.close() |
|
180 | ||
181 | if cursor_energy: |
|
182 | cursor_energy.close() |
|
183 | if cnx_energy: |
|
184 | cnx_energy.close() |
|
185 | ||
186 | if cursor_carbon: |
|
187 | cursor_carbon.close() |
|
188 | if cnx_carbon: |
|
189 | cnx_carbon.close() |
|
190 | raise falcon.HTTPError(status=falcon.HTTP_404, title='API.NOT_FOUND', |
|
191 | description='API.OFFLINE_METER_NOT_FOUND') |
|
192 | ||
193 | offline_meter = dict() |
|
194 | offline_meter['id'] = row_offline_meter[0] |
|
195 | offline_meter['name'] = row_offline_meter[1] |
|
196 | offline_meter['cost_center_id'] = row_offline_meter[2] |
|
197 | offline_meter['energy_category_id'] = row_offline_meter[3] |
|
198 | offline_meter['energy_category_name'] = row_offline_meter[4] |
|
199 | offline_meter['unit_of_measure'] = config.currency_unit |
|
200 | offline_meter['kgce'] = row_offline_meter[6] |
|
201 | offline_meter['kgco2e'] = row_offline_meter[7] |
|
202 | ||
203 | ################################################################################################################ |
|
204 | # Step 3: query base period energy consumption |
|
205 | ################################################################################################################ |
|
206 | query = (" SELECT start_datetime_utc, actual_value " |
|
207 | " FROM tbl_offline_meter_hourly " |
|
208 | " WHERE offline_meter_id = %s " |
|
209 | " AND start_datetime_utc >= %s " |
|
210 | " AND start_datetime_utc < %s " |
|
211 | " ORDER BY start_datetime_utc ") |
|
212 | cursor_energy.execute(query, (offline_meter['id'], base_start_datetime_utc, base_end_datetime_utc)) |
|
213 | rows_offline_meter_hourly = cursor_energy.fetchall() |
|
214 | ||
215 | rows_offline_meter_periodically = utilities.aggregate_hourly_data_by_period(rows_offline_meter_hourly, |
|
216 | base_start_datetime_utc, |
|
217 | base_end_datetime_utc, |
|
218 | period_type) |
|
219 | base = dict() |
|
220 | base['timestamps'] = list() |
|
221 | base['values'] = list() |
|
222 | base['total_in_category'] = Decimal(0.0) |
|
223 | base['total_in_kgce'] = Decimal(0.0) |
|
224 | base['total_in_kgco2e'] = Decimal(0.0) |
|
225 | ||
226 | for row_offline_meter_periodically in rows_offline_meter_periodically: |
|
227 | current_datetime_local = row_offline_meter_periodically[0].replace(tzinfo=timezone.utc) + \ |
|
228 | timedelta(minutes=timezone_offset) |
|
229 | if period_type == 'hourly': |
|
230 | current_datetime = current_datetime_local.isoformat()[0:19] |
|
231 | elif period_type == 'daily': |
|
232 | current_datetime = current_datetime_local.isoformat()[0:10] |
|
233 | elif period_type == 'weekly': |
|
234 | current_datetime = current_datetime_local.isoformat()[0:10] |
|
235 | elif period_type == 'monthly': |
|
236 | current_datetime = current_datetime_local.isoformat()[0:7] |
|
237 | elif period_type == 'yearly': |
|
238 | current_datetime = current_datetime_local.isoformat()[0:4] |
|
239 | ||
240 | actual_value = Decimal(0.0) if row_offline_meter_periodically[1] is None \ |
|
241 | else row_offline_meter_periodically[1] |
|
242 | base['timestamps'].append(current_datetime) |
|
243 | base['total_in_kgce'] += actual_value * offline_meter['kgce'] |
|
244 | base['total_in_kgco2e'] += actual_value * offline_meter['kgco2e'] |
|
245 | ||
246 | ################################################################################################################ |
|
247 | # Step 4: query base period energy carbon dioxide emissions |
|
248 | ################################################################################################################ |
|
249 | query = (" SELECT start_datetime_utc, actual_value " |
|
250 | " FROM tbl_offline_meter_hourly " |
|
251 | " WHERE offline_meter_id = %s " |
|
252 | " AND start_datetime_utc >= %s " |
|
253 | " AND start_datetime_utc < %s " |
|
254 | " ORDER BY start_datetime_utc ") |
|
255 | cursor_carbon.execute(query, (offline_meter['id'], base_start_datetime_utc, base_end_datetime_utc)) |
|
256 | rows_offline_meter_hourly = cursor_carbon.fetchall() |
|
257 | ||
258 | rows_offline_meter_periodically = utilities.aggregate_hourly_data_by_period(rows_offline_meter_hourly, |
|
259 | base_start_datetime_utc, |
|
260 | base_end_datetime_utc, |
|
261 | period_type) |
|
262 | ||
263 | base['values'] = list() |
|
264 | base['total_in_category'] = Decimal(0.0) |
|
265 | ||
266 | for row_offline_meter_periodically in rows_offline_meter_periodically: |
|
267 | actual_value = Decimal(0.0) if row_offline_meter_periodically[1] is None \ |
|
268 | else row_offline_meter_periodically[1] |
|
269 | base['values'].append(actual_value) |
|
270 | base['total_in_category'] += actual_value |
|
271 | ||
272 | ################################################################################################################ |
|
273 | # Step 5: query reporting period energy consumption |
|
274 | ################################################################################################################ |
|
275 | query = (" SELECT start_datetime_utc, actual_value " |
|
276 | " FROM tbl_offline_meter_hourly " |
|
277 | " WHERE offline_meter_id = %s " |
|
278 | " AND start_datetime_utc >= %s " |
|
279 | " AND start_datetime_utc < %s " |
|
280 | " ORDER BY start_datetime_utc ") |
|
281 | cursor_energy.execute(query, (offline_meter['id'], reporting_start_datetime_utc, reporting_end_datetime_utc)) |
|
282 | rows_offline_meter_hourly = cursor_energy.fetchall() |
|
283 | ||
284 | rows_offline_meter_periodically = utilities.aggregate_hourly_data_by_period(rows_offline_meter_hourly, |
|
285 | reporting_start_datetime_utc, |
|
286 | reporting_end_datetime_utc, |
|
287 | period_type) |
|
288 | reporting = dict() |
|
289 | reporting['timestamps'] = list() |
|
290 | reporting['values'] = list() |
|
291 | reporting['rates'] = list() |
|
292 | reporting['total_in_category'] = Decimal(0.0) |
|
293 | reporting['total_in_kgce'] = Decimal(0.0) |
|
294 | reporting['total_in_kgco2e'] = Decimal(0.0) |
|
295 | ||
296 | for row_offline_meter_periodically in rows_offline_meter_periodically: |
|
297 | current_datetime_local = row_offline_meter_periodically[0].replace(tzinfo=timezone.utc) + \ |
|
298 | timedelta(minutes=timezone_offset) |
|
299 | if period_type == 'hourly': |
|
300 | current_datetime = current_datetime_local.isoformat()[0:19] |
|
301 | elif period_type == 'daily': |
|
302 | current_datetime = current_datetime_local.isoformat()[0:10] |
|
303 | elif period_type == 'weekly': |
|
304 | current_datetime = current_datetime_local.isoformat()[0:10] |
|
305 | elif period_type == 'monthly': |
|
306 | current_datetime = current_datetime_local.isoformat()[0:7] |
|
307 | elif period_type == 'yearly': |
|
308 | current_datetime = current_datetime_local.isoformat()[0:4] |
|
309 | ||
310 | actual_value = Decimal(0.0) if row_offline_meter_periodically[1] is None \ |
|
311 | else row_offline_meter_periodically[1] |
|
312 | ||
313 | reporting['timestamps'].append(current_datetime) |
|
314 | reporting['total_in_kgce'] += actual_value * offline_meter['kgce'] |
|
315 | reporting['total_in_kgco2e'] += actual_value * offline_meter['kgco2e'] |
|
316 | ||
317 | ################################################################################################################ |
|
318 | # Step 6: query reporting period energy carbon dioxide emissions |
|
319 | ################################################################################################################ |
|
320 | query = (" SELECT start_datetime_utc, actual_value " |
|
321 | " FROM tbl_offline_meter_hourly " |
|
322 | " WHERE offline_meter_id = %s " |
|
323 | " AND start_datetime_utc >= %s " |
|
324 | " AND start_datetime_utc < %s " |
|
325 | " ORDER BY start_datetime_utc ") |
|
326 | cursor_carbon.execute(query, (offline_meter['id'], reporting_start_datetime_utc, reporting_end_datetime_utc)) |
|
327 | rows_offline_meter_hourly = cursor_carbon.fetchall() |
|
328 | ||
329 | rows_offline_meter_periodically = utilities.aggregate_hourly_data_by_period(rows_offline_meter_hourly, |
|
330 | reporting_start_datetime_utc, |
|
331 | reporting_end_datetime_utc, |
|
332 | period_type) |
|
333 | ||
334 | for row_offline_meter_periodically in rows_offline_meter_periodically: |
|
335 | actual_value = Decimal(0.0) if row_offline_meter_periodically[1] is None \ |
|
336 | else row_offline_meter_periodically[1] |
|
337 | ||
338 | reporting['values'].append(actual_value) |
|
339 | reporting['total_in_category'] += actual_value |
|
340 | ||
341 | for index, value in enumerate(reporting['values']): |
|
342 | if index < len(base['values']) and base['values'][index] != 0 and value != 0: |
|
343 | reporting['rates'].append((value - base['values'][index]) / base['values'][index]) |
|
344 | else: |
|
345 | reporting['rates'].append(None) |
|
346 | ||
347 | ################################################################################################################ |
|
348 | # Step 7: query tariff data |
|
349 | ################################################################################################################ |
|
350 | parameters_data = dict() |
|
351 | parameters_data['names'] = list() |
|
352 | parameters_data['timestamps'] = list() |
|
353 | parameters_data['values'] = list() |
|
354 | ||
355 | if config.is_tariff_appended and not is_quick_mode: |
|
356 | tariff_dict = utilities.get_energy_category_tariffs(offline_meter['cost_center_id'], |
|
357 | offline_meter['energy_category_id'], |
|
358 | reporting_start_datetime_utc, |
|
359 | reporting_end_datetime_utc) |
|
360 | tariff_timestamp_list = list() |
|
361 | tariff_value_list = list() |
|
362 | for k, v in tariff_dict.items(): |
|
363 | # convert k from utc to local |
|
364 | k = k + timedelta(minutes=timezone_offset) |
|
365 | tariff_timestamp_list.append(k.isoformat()[0:19]) |
|
366 | tariff_value_list.append(v) |
|
367 | ||
368 | parameters_data['names'].append(_('Tariff') + '-' + offline_meter['energy_category_name']) |
|
369 | parameters_data['timestamps'].append(tariff_timestamp_list) |
|
370 | parameters_data['values'].append(tariff_value_list) |
|
371 | ||
372 | ################################################################################################################ |
|
373 | # Step 8: construct the report |
|
374 | ################################################################################################################ |
|
375 | if cursor_system: |
|
376 | cursor_system.close() |
|
377 | if cnx_system: |
|
378 | cnx_system.close() |
|
379 | ||
380 | if cursor_energy: |
|
381 | cursor_energy.close() |
|
382 | if cnx_energy: |
|
383 | cnx_energy.close() |
|
384 | ||
385 | if cursor_carbon: |
|
386 | cursor_carbon.close() |
|
387 | if cnx_carbon: |
|
388 | cnx_carbon.close() |
|
389 | ||
390 | result = {"offline_meter": { |
|
391 | "cost_center_id": offline_meter['cost_center_id'], |
|
392 | "energy_category_id": offline_meter['energy_category_id'], |
|
393 | "energy_category_name": offline_meter['energy_category_name'], |
|
394 | "unit_of_measure": 'KG', |
|
395 | "kgce": offline_meter['kgce'], |
|
396 | "kgco2e": offline_meter['kgco2e'], |
|
397 | }, "base_period": { |
|
398 | "total_in_category": base['total_in_category'], |
|
399 | "total_in_kgce": base['total_in_kgce'], |
|
400 | "total_in_kgco2e": base['total_in_kgco2e'], |
|
401 | "timestamps": base['timestamps'], |
|
402 | "values": base['values'], |
|
403 | }, "reporting_period": { |
|
404 | "increment_rate": |
|
405 | (reporting['total_in_category'] - base['total_in_category']) / base['total_in_category'] |
|
406 | if base['total_in_category'] != Decimal(0.0) else None, |
|
407 | "total_in_category": reporting['total_in_category'], |
|
408 | "total_in_kgce": reporting['total_in_kgce'], |
|
409 | "total_in_kgco2e": reporting['total_in_kgco2e'], |
|
410 | "timestamps": reporting['timestamps'], |
|
411 | "values": reporting['values'], |
|
412 | "rates": reporting['rates'], |
|
413 | }, "parameters": { |
|
414 | "names": parameters_data['names'], |
|
415 | "timestamps": parameters_data['timestamps'], |
|
416 | "values": parameters_data['values'] |
|
417 | }, 'excel_bytes_base64': None} |
|
418 | ||
419 | # export result to Excel file and then encode the file to base64 string |
|
420 | if not is_quick_mode: |
|
421 | result['excel_bytes_base64'] = \ |
|
422 | excelexporters.offlinemetercarbon.export(result, |
|
423 | offline_meter['name'], |
|
424 | base_period_start_datetime_local, |
|
425 | base_period_end_datetime_local, |
|
426 | reporting_period_start_datetime_local, |
|
427 | reporting_period_end_datetime_local, |
|
428 | period_type, |
|
429 | language) |
|
430 | ||
431 | resp.text = json.dumps(result) |
|
432 |