Passed
Push — master ( d7d153...467911 )
by
unknown
12:37
created

reports.virtualmetercomparison.Reporting.on_get()   F

Complexity

Conditions 101

Size

Total Lines 512
Code Lines 383

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 383
dl 0
loc 512
rs 0
c 0
b 0
f 0
cc 101
nop 2

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

Complexity

Complex classes like reports.virtualmetercomparison.Reporting.on_get() often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1
"""
2
Virtual Meter Comparison Report API
3
4
This module provides REST API endpoints for generating virtual meter comparison reports.
5
It compares energy consumption data between different virtual meters or time periods,
6
providing insights into performance differences and optimization opportunities.
7
8
Key Features:
9
- Virtual Meter-to-Virtual Meter comparison analysis
10
- Time period comparison
11
- Performance difference identification
12
- Comparative metrics calculation
13
- Excel export functionality
14
- Benchmarking insights
15
16
Report Components:
17
- Virtual Meter comparison summary
18
- Performance difference analysis
19
- Comparative metrics and KPIs
20
- Benchmarking data
21
- Performance gap identification
22
- Optimization recommendations
23
24
The module uses Falcon framework for REST API and includes:
25
- Database queries for comparison data
26
- Comparative analysis algorithms
27
- Performance benchmarking tools
28
- Excel export via excelexporters
29
- Multi-language support
30
- User authentication and authorization
31
"""
32
33
import re
34
from datetime import datetime, timedelta, timezone
35
from decimal import Decimal
36
import falcon
37
import mysql.connector
38
import simplejson as json
39
import config
40
import excelexporters.virtualmetercomparison  # 对应虚拟表Excel导出模块
41
from core import utilities
42
from core.useractivity import access_control, api_key_control
43
44
45
class Reporting:
46
    def __init__(self):
47
        """"Initializes Reporting"""
48
        pass
49
50
    @staticmethod
51
    def on_options(req, resp):
52
        _ = req
53
        resp.status = falcon.HTTP_200
54
55
    ####################################################################################################################
56
    # PROCEDURES
57
    # Step 1: valid parameters
58
    # Step 2: query the virtual meter and energy category
59
    # Step 3: parse equation and get associated points
60
    # Step 4: query reporting period energy consumption
61
    # Step 5: query associated points data
62
    # Step 6: construct the report
63
    ####################################################################################################################
64
    @staticmethod
65
    def on_get(req, resp):
66
        if 'API-KEY' not in req.headers or \
67
                not isinstance(req.headers['API-KEY'], str) or \
68
                len(str.strip(req.headers['API-KEY'])) == 0:
69
            access_control(req)
70
        else:
71
            api_key_control(req)
72
        print(req.params)
73
        # this procedure accepts virtual meter id or virtual meter uuid to identify a virtual meter
74
        virtual_meter_id1 = req.params.get('virtualmeterid1')
75
        virtual_meter_uuid1 = req.params.get('virtualmeteruuid1')
76
        virtual_meter_id2 = req.params.get('virtualmeterid2')
77
        virtual_meter_uuid2 = req.params.get('virtualmeteruuid2')
78
        period_type = req.params.get('periodtype')
79
        reporting_period_start_datetime_local = req.params.get('reportingperiodstartdatetime')
80
        reporting_period_end_datetime_local = req.params.get('reportingperiodenddatetime')
81
        language = req.params.get('language')
82
        quick_mode = req.params.get('quickmode')
83
84
        ################################################################################################################
85
        # Step 1: valid parameters
86
        ################################################################################################################
87
        if virtual_meter_id1 is None and virtual_meter_uuid1 is None:
88
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST', description='API.INVALID_VIRTUAL_METER_ID')
89
90
        if virtual_meter_id1 is not None:
91
            virtual_meter_id1 = str.strip(virtual_meter_id1)
92
            if not virtual_meter_id1.isdigit() or int(virtual_meter_id1) <= 0:
93
                raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
94
                                       description='API.INVALID_VIRTUAL_METER_ID')
95
96
        if virtual_meter_uuid1 is not None:
97
            regex = re.compile(r'^[a-f0-9]{8}-?[a-f0-9]{4}-?4[a-f0-9]{3}-?[89ab][a-f0-9]{3}-?[a-f0-9]{12}\Z', re.I)
98
            match = regex.match(str.strip(virtual_meter_uuid1))
99
            if not bool(match):
100
                raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
101
                                       description='API.INVALID_VIRTUAL_METER_UUID')
102
103
        if virtual_meter_id2 is None and virtual_meter_uuid2 is None:
104
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
105
                                   description='API.INVALID_VIRTUAL_METER_ID')
106
107
        if virtual_meter_id2 is not None:
108
            virtual_meter_id2 = str.strip(virtual_meter_id2)
109
            if not virtual_meter_id2.isdigit() or int(virtual_meter_id2) <= 0:
110
                raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
111
                                       description='API.INVALID_VIRTUAL_METER_ID')
112
113
        if virtual_meter_uuid2 is not None:
114
            regex = re.compile(r'^[a-f0-9]{8}-?[a-f0-9]{4}-?4[a-f0-9]{3}-?[89ab][a-f0-9]{3}-?[a-f0-9]{12}\Z', re.I)
115
            match = regex.match(str.strip(virtual_meter_uuid2))
116
            if not bool(match):
117
                raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
118
                                       description='API.INVALID_VIRTUAL_METER_UUID')
119
120
        if period_type is None:
121
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
122
                                   description='API.INVALID_PERIOD_TYPE')
123
        else:
124
            period_type = str.strip(period_type)
125
            if period_type not in ['hourly', 'daily', 'weekly', 'monthly', 'yearly']:
126
                raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
127
                                       description='API.INVALID_PERIOD_TYPE')
128
129
        timezone_offset = int(config.utc_offset[1:3]) * 60 + int(config.utc_offset[4:6])
130
        if config.utc_offset[0] == '-':
131
            timezone_offset = -timezone_offset
132
133
        if reporting_period_start_datetime_local is None:
134
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
135
                                   description="API.INVALID_REPORTING_PERIOD_START_DATETIME")
136
        else:
137
            reporting_period_start_datetime_local = str.strip(reporting_period_start_datetime_local)
138
            try:
139
                reporting_start_datetime_utc = datetime.strptime(reporting_period_start_datetime_local,
140
                                                                 '%Y-%m-%dT%H:%M:%S')
141
            except ValueError:
142
                raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
143
                                       description="API.INVALID_REPORTING_PERIOD_START_DATETIME")
144
            reporting_start_datetime_utc = \
145
                reporting_start_datetime_utc.replace(tzinfo=timezone.utc) - timedelta(minutes=timezone_offset)
146
            # nomalize the start datetime
147
            if config.minutes_to_count == 30 and reporting_start_datetime_utc.minute >= 30:
148
                reporting_start_datetime_utc = reporting_start_datetime_utc.replace(minute=30, second=0, microsecond=0)
149
            else:
150
                reporting_start_datetime_utc = reporting_start_datetime_utc.replace(minute=0, second=0, microsecond=0)
151
152
        if reporting_period_end_datetime_local is None:
153
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
154
                                   description="API.INVALID_REPORTING_PERIOD_END_DATETIME")
155
        else:
156
            reporting_period_end_datetime_local = str.strip(reporting_period_end_datetime_local)
157
            try:
158
                reporting_end_datetime_utc = datetime.strptime(reporting_period_end_datetime_local,
159
                                                               '%Y-%m-%dT%H:%M:%S')
160
            except ValueError:
161
                raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
162
                                       description="API.INVALID_REPORTING_PERIOD_END_DATETIME")
163
            reporting_end_datetime_utc = reporting_end_datetime_utc.replace(tzinfo=timezone.utc) - \
164
                timedelta(minutes=timezone_offset)
165
166
        if reporting_start_datetime_utc >= reporting_end_datetime_utc:
167
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
168
                                   description='API.INVALID_REPORTING_PERIOD_END_DATETIME')
169
170
        # if turn quick mode on, do not return parameters data and excel file
171
        is_quick_mode = False
172
        if quick_mode is not None and \
173
                len(str.strip(quick_mode)) > 0 and \
174
                str.lower(str.strip(quick_mode)) in ('true', 't', 'on', 'yes', 'y'):
175
            is_quick_mode = True
176
177
        ################################################################################################################
178
        # Step 2: query the virtual meter and energy category
179
        ################################################################################################################
180
        cnx_system = mysql.connector.connect(**config.myems_system_db)
181
        cursor_system = cnx_system.cursor()
182
183
        cnx_energy = mysql.connector.connect(** config.myems_energy_db)
184
        cursor_energy = cnx_energy.cursor()
185
186
        cnx_historical = mysql.connector.connect(**config.myems_historical_db)
187
        cursor_historical = cnx_historical.cursor()
188
189
        if virtual_meter_id1 is not None:
190
            cursor_system.execute(" SELECT vm.id, vm.name, vm.energy_category_id, ec.name, ec.unit_of_measure, vm.equation "
191
                                  " FROM tbl_virtual_meters vm, tbl_energy_categories ec "
192
                                  " WHERE vm.id = %s AND vm.energy_category_id = ec.id ", (virtual_meter_id1,))
193
            row_virtual_meter1 = cursor_system.fetchone()
194
        elif virtual_meter_uuid1 is not None:
195
            cursor_system.execute(" SELECT vm.id, vm.name, vm.energy_category_id, ec.name, ec.unit_of_measure, vm.equation "
196
                                  " FROM tbl_virtual_meters vm, tbl_energy_categories ec "
197
                                  " WHERE vm.uuid = %s AND vm.energy_category_id = ec.id ", (virtual_meter_uuid1,))
198
            row_virtual_meter1 = cursor_system.fetchone()
199
200
        if row_virtual_meter1 is None:
0 ignored issues
show
introduced by
The variable row_virtual_meter1 does not seem to be defined for all execution paths.
Loading history...
201
            if cursor_system:
202
                cursor_system.close()
203
            if cnx_system:
204
                cnx_system.close()
205
206
            if cursor_energy:
207
                cursor_energy.close()
208
            if cnx_energy:
209
                cnx_energy.close()
210
211
            if cursor_historical:
212
                cursor_historical.close()
213
            if cnx_historical:
214
                cnx_historical.close()
215
            raise falcon.HTTPError(status=falcon.HTTP_404, title='API.NOT_FOUND', description='API.VIRTUAL_METER_NOT_FOUND')
216
217
        virtual_meter1 = dict()
218
        virtual_meter1['id'] = row_virtual_meter1[0]
219
        virtual_meter1['name'] = row_virtual_meter1[1]
220
        virtual_meter1['energy_category_id'] = row_virtual_meter1[2]
221
        virtual_meter1['energy_category_name'] = row_virtual_meter1[3]
222
        virtual_meter1['unit_of_measure'] = row_virtual_meter1[4]
223
        virtual_meter1['equation'] = row_virtual_meter1[5]  
224
225
        if virtual_meter_id2 is not None:
226
            cursor_system.execute(" SELECT vm.id, vm.name, vm.energy_category_id, ec.name, ec.unit_of_measure, vm.equation "
227
                                  " FROM tbl_virtual_meters vm, tbl_energy_categories ec "
228
                                  " WHERE vm.id = %s AND vm.energy_category_id = ec.id ", (virtual_meter_id2,))
229
            row_virtual_meter2 = cursor_system.fetchone()
230
        elif virtual_meter_uuid2 is not None:
231
            cursor_system.execute(" SELECT vm.id, vm.name, vm.energy_category_id, ec.name, ec.unit_of_measure, vm.equation "
232
                                  " FROM tbl_virtual_meters vm, tbl_energy_categories ec "
233
                                  " WHERE vm.uuid = %s AND vm.energy_category_id = ec.id ", (virtual_meter_uuid2,))
234
            row_virtual_meter2 = cursor_system.fetchone()
235
236
        if row_virtual_meter2 is None:
0 ignored issues
show
introduced by
The variable row_virtual_meter2 does not seem to be defined for all execution paths.
Loading history...
237
            if cursor_system:
238
                cursor_system.close()
239
            if cnx_system:
240
                cnx_system.close()
241
242
            if cursor_energy:
243
                cursor_energy.close()
244
            if cnx_energy:
245
                cnx_energy.close()
246
247
            if cursor_historical:
248
                cursor_historical.close()
249
            if cnx_historical:
250
                cnx_historical.close()
251
            raise falcon.HTTPError(status=falcon.HTTP_404, title='API.NOT_FOUND', description='API.VIRTUAL_METER_NOT_FOUND')
252
253
        virtual_meter2 = dict()
254
        virtual_meter2['id'] = row_virtual_meter2[0]
255
        virtual_meter2['name'] = row_virtual_meter2[1]
256
        virtual_meter2['energy_category_id'] = row_virtual_meter2[2]
257
        virtual_meter2['energy_category_name'] = row_virtual_meter2[3]
258
        virtual_meter2['unit_of_measure'] = row_virtual_meter2[4]
259
        virtual_meter2['equation'] = row_virtual_meter2[5]  
260
261
        ################################################################################################################
262
        # Step 3: parse equation and get associated points 
263
        ################################################################################################################
264
        def parse_equation_and_get_points(equation, cursor_system):
265
            if not equation:
266
                return []
267
            variables = re.findall(r'x\d+', equation)  
268
            if not variables:
269
                return []
270
            unique_vars = list(set(variables))
271
           
272
            placeholders = ', '.join(['%s'] * len(unique_vars))
273
            cursor_system.execute(f" SELECT p.id, p.name, p.units, p.object_type "
274
                                  f" FROM tbl_points p "
275
                                  f" WHERE p.name IN ({placeholders}) ", tuple(unique_vars))
276
            rows = cursor_system.fetchall()
277
            return [{"id": row[0], "name": row[1], "units": row[2], "object_type": row[3]} for row in rows]
278
279
        point_list1 = parse_equation_and_get_points(virtual_meter1['equation'], cursor_system)
280
        point_list2 = parse_equation_and_get_points(virtual_meter2['equation'], cursor_system)
281
282
        ################################################################################################################
283
        # Step 4: query reporting period energy consumption
284
        ################################################################################################################
285
        query1 = (" SELECT start_datetime_utc, actual_value "
286
                  " FROM tbl_virtual_meter_hourly "
287
                  " WHERE virtual_meter_id = %s "
288
                  " AND start_datetime_utc >= %s "
289
                  " AND start_datetime_utc < %s "
290
                  " ORDER BY start_datetime_utc ")
291
        cursor_energy.execute(query1, (virtual_meter1['id'], reporting_start_datetime_utc, reporting_end_datetime_utc))
292
        rows_virtual_meter1_hourly = cursor_energy.fetchall()
293
294
        rows_virtual_meter1_periodically = utilities.aggregate_hourly_data_by_period(rows_virtual_meter1_hourly,
295
                                                                                     reporting_start_datetime_utc,
296
                                                                                     reporting_end_datetime_utc,
297
                                                                                     period_type)
298
        reporting1 = dict()
299
        reporting1['timestamps'] = list()
300
        reporting1['values'] = list()
301
        reporting1['total_in_category'] = Decimal(0.0)
302
303
        for row_virtual_meter1_periodically in rows_virtual_meter1_periodically:
304
            current_datetime_local = row_virtual_meter1_periodically[0].replace(tzinfo=timezone.utc) + \
305
                timedelta(minutes=timezone_offset)
306
            if period_type == 'hourly':
307
                current_datetime = current_datetime_local.isoformat()[0:19]
308
            elif period_type == 'daily':
309
                current_datetime = current_datetime_local.isoformat()[0:10]
310
            elif period_type == 'weekly':
311
                current_datetime = current_datetime_local.isoformat()[0:10]
312
            elif period_type == 'monthly':
313
                current_datetime = current_datetime_local.isoformat()[0:7]
314
            elif period_type == 'yearly':
315
                current_datetime = current_datetime_local.isoformat()[0:4]
316
317
            actual_value = Decimal(0.0) if row_virtual_meter1_periodically[1] is None else row_virtual_meter1_periodically[1]
318
319
            reporting1['timestamps'].append(current_datetime)
0 ignored issues
show
introduced by
The variable current_datetime does not seem to be defined for all execution paths.
Loading history...
320
            reporting1['values'].append(actual_value)
321
            reporting1['total_in_category'] += actual_value
322
323
        query2 = (" SELECT start_datetime_utc, actual_value "
324
                  " FROM tbl_virtual_meter_hourly "
325
                  " WHERE virtual_meter_id = %s "
326
                  " AND start_datetime_utc >= %s "
327
                  " AND start_datetime_utc < %s "
328
                  " ORDER BY start_datetime_utc ")
329
        cursor_energy.execute(query2, (virtual_meter2['id'], reporting_start_datetime_utc, reporting_end_datetime_utc))
330
        rows_virtual_meter2_hourly = cursor_energy.fetchall()
331
332
        rows_virtual_meter2_periodically = utilities.aggregate_hourly_data_by_period(rows_virtual_meter2_hourly,
333
                                                                                     reporting_start_datetime_utc,
334
                                                                                     reporting_end_datetime_utc,
335
                                                                                     period_type)
336
        reporting2 = dict()
337
        diff = dict()
338
        reporting2['timestamps'] = list()
339
        reporting2['values'] = list()
340
        reporting2['total_in_category'] = Decimal(0.0)
341
        diff['values'] = list()
342
        diff['total_in_category'] = Decimal(0.0)
343
344
        for row_virtual_meter2_periodically in rows_virtual_meter2_periodically:
345
            current_datetime_local = row_virtual_meter2_periodically[0].replace(tzinfo=timezone.utc) + \
346
                                     timedelta(minutes=timezone_offset)
347
            if period_type == 'hourly':
348
                current_datetime = current_datetime_local.isoformat()[0:19]
349
            elif period_type == 'daily':
350
                current_datetime = current_datetime_local.isoformat()[0:10]
351
            elif period_type == 'weekly':
352
                current_datetime = current_datetime_local.isoformat()[0:10]
353
            elif period_type == 'monthly':
354
                current_datetime = current_datetime_local.isoformat()[0:7]
355
            elif period_type == 'yearly':
356
                current_datetime = current_datetime_local.isoformat()[0:4]
357
358
            actual_value = Decimal(0.0) if row_virtual_meter2_periodically[1] is None else row_virtual_meter2_periodically[1]
359
360
            reporting2['timestamps'].append(current_datetime)
361
            reporting2['values'].append(actual_value)
362
            reporting2['total_in_category'] += actual_value
363
364
        for virtual_meter1_value, virtual_meter2_value in zip(reporting1['values'], reporting2['values']):
365
            diff['values'].append(virtual_meter1_value - virtual_meter2_value)
366
            diff['total_in_category'] += virtual_meter1_value - virtual_meter2_value
367
368
        ################################################################################################################
369
        # Step 5: query associated points data
370
        ################################################################################################################
371
        parameters_data1 = dict()
372
        parameters_data1['names'] = list()
373
        parameters_data1['timestamps'] = list()
374
        parameters_data1['values'] = list()
375
376
        if not is_quick_mode:
377
            for point in point_list1:
378
                point_values = []
379
                point_timestamps = []
380
                if point['object_type'] == 'ENERGY_VALUE':
381
                    query = (" SELECT utc_date_time, actual_value "
382
                             " FROM tbl_energy_value "
383
                             " WHERE point_id = %s "
384
                             "       AND utc_date_time BETWEEN %s AND %s "
385
                             " ORDER BY utc_date_time ")
386
                    cursor_historical.execute(query, (point['id'],
387
                                                      reporting_start_datetime_utc,
388
                                                      reporting_end_datetime_utc))
389
                    rows = cursor_historical.fetchall()
390
391
                    if rows is not None and len(rows) > 0:
392
                        for row in rows:
393
                            current_datetime_local = row[0].replace(tzinfo=timezone.utc) + \
394
                                                     timedelta(minutes=timezone_offset)
395
                            current_datetime = current_datetime_local.isoformat()[0:19]
396
                            point_timestamps.append(current_datetime)
397
                            point_values.append(row[1])
398
                elif point['object_type'] == 'ANALOG_VALUE':
399
                    query = (" SELECT utc_date_time, actual_value "
400
                             " FROM tbl_analog_value "
401
                             " WHERE point_id = %s "
402
                             "       AND utc_date_time BETWEEN %s AND %s "
403
                             " ORDER BY utc_date_time ")
404
                    cursor_historical.execute(query, (point['id'],
405
                                                      reporting_start_datetime_utc,
406
                                                      reporting_end_datetime_utc))
407
                    rows = cursor_historical.fetchall()
408
409
                    if rows is not None and len(rows) > 0:
410
                        for row in rows:
411
                            current_datetime_local = row[0].replace(tzinfo=timezone.utc) + \
412
                                                     timedelta(minutes=timezone_offset)
413
                            current_datetime = current_datetime_local.isoformat()[0:19]
414
                            point_timestamps.append(current_datetime)
415
                            point_values.append(row[1])
416
                elif point['object_type'] == 'DIGITAL_VALUE':
417
                    query = (" SELECT utc_date_time, actual_value "
418
                             " FROM tbl_digital_value "
419
                             " WHERE point_id = %s "
420
                             "       AND utc_date_time BETWEEN %s AND %s "
421
                             " ORDER BY utc_date_time ")
422
                    cursor_historical.execute(query, (point['id'],
423
                                                      reporting_start_datetime_utc,
424
                                                      reporting_end_datetime_utc))
425
                    rows = cursor_historical.fetchall()
426
427
                    if rows is not None and len(rows) > 0:
428
                        for row in rows:
429
                            current_datetime_local = row[0].replace(tzinfo=timezone.utc) + \
430
                                                     timedelta(minutes=timezone_offset)
431
                            current_datetime = current_datetime_local.isoformat()[0:19]
432
                            point_timestamps.append(current_datetime)
433
                            point_values.append(row[1])
434
435
                parameters_data1['names'].append(point['name'] + ' (' + point['units'] + ')')
436
                parameters_data1['timestamps'].append(point_timestamps)
437
                parameters_data1['values'].append(point_values)
438
439
        parameters_data2 = dict()
440
        parameters_data2['names'] = list()
441
        parameters_data2['timestamps'] = list()
442
        parameters_data2['values'] = list()
443
        if not is_quick_mode:
444
            for point in point_list2:
445
                point_values = []
446
                point_timestamps = []
447
                if point['object_type'] == 'ENERGY_VALUE':
448
                    query = (" SELECT utc_date_time, actual_value "
449
                             " FROM tbl_energy_value "
450
                             " WHERE point_id = %s "
451
                             "       AND utc_date_time BETWEEN %s AND %s "
452
                             " ORDER BY utc_date_time ")
453
                    cursor_historical.execute(query, (point['id'],
454
                                                      reporting_start_datetime_utc,
455
                                                      reporting_end_datetime_utc))
456
                    rows = cursor_historical.fetchall()
457
458
                    if rows is not None and len(rows) > 0:
459
                        for row in rows:
460
                            current_datetime_local = row[0].replace(tzinfo=timezone.utc) + \
461
                                                     timedelta(minutes=timezone_offset)
462
                            current_datetime = current_datetime_local.isoformat()[0:19]
463
                            point_timestamps.append(current_datetime)
464
                            point_values.append(row[1])
465
                elif point['object_type'] == 'ANALOG_VALUE':
466
                    query = (" SELECT utc_date_time, actual_value "
467
                             " FROM tbl_analog_value "
468
                             " WHERE point_id = %s "
469
                             "       AND utc_date_time BETWEEN %s AND %s "
470
                             " ORDER BY utc_date_time ")
471
                    cursor_historical.execute(query, (point['id'],
472
                                                      reporting_start_datetime_utc,
473
                                                      reporting_end_datetime_utc))
474
                    rows = cursor_historical.fetchall()
475
476
                    if rows is not None and len(rows) > 0:
477
                        for row in rows:
478
                            current_datetime_local = row[0].replace(tzinfo=timezone.utc) + \
479
                                                     timedelta(minutes=timezone_offset)
480
                            current_datetime = current_datetime_local.isoformat()[0:19]
481
                            point_timestamps.append(current_datetime)
482
                            point_values.append(row[1])
483
                elif point['object_type'] == 'DIGITAL_VALUE':
484
                    query = (" SELECT utc_date_time, actual_value "
485
                             " FROM tbl_digital_value "
486
                             " WHERE point_id = %s "
487
                             "       AND utc_date_time BETWEEN %s AND %s "
488
                             " ORDER BY utc_date_time ")
489
                    cursor_historical.execute(query, (point['id'],
490
                                                      reporting_start_datetime_utc,
491
                                                      reporting_end_datetime_utc))
492
                    rows = cursor_historical.fetchall()
493
494
                    if rows is not None and len(rows) > 0:
495
                        for row in rows:
496
                            current_datetime_local = row[0].replace(tzinfo=timezone.utc) + \
497
                                                     timedelta(minutes=timezone_offset)
498
                            current_datetime = current_datetime_local.isoformat()[0:19]
499
                            point_timestamps.append(current_datetime)
500
                            point_values.append(row[1])
501
502
                parameters_data2['names'].append(point['name'] + ' (' + point['units'] + ')')
503
                parameters_data2['timestamps'].append(point_timestamps)
504
                parameters_data2['values'].append(point_values)
505
506
        ################################################################################################################
507
        # Step 6: construct the report
508
        ################################################################################################################
509
        if cursor_system:
510
            cursor_system.close()
511
        if cnx_system:
512
            cnx_system.close()
513
514
        if cursor_energy:
515
            cursor_energy.close()
516
        if cnx_energy:
517
            cnx_energy.close()
518
519
        if cursor_historical:
520
            cursor_historical.close()
521
        if cnx_historical:
522
            cnx_historical.close()
523
524
        result = {
525
            "virtualmeter1": {
526
                "name": virtual_meter1['name'],
527
                "energy_category_id": virtual_meter1['energy_category_id'],
528
                "energy_category_name": virtual_meter1['energy_category_name'],
529
                "unit_of_measure": virtual_meter1['unit_of_measure'],
530
                "equation": virtual_meter1['equation']  # 新增:返回方程式
531
            },
532
            "reporting_period1": {
533
                "total_in_category": reporting1['total_in_category'],
534
                "timestamps": reporting1['timestamps'],
535
                "values": reporting1['values'],
536
            },
537
            "parameters1": {
538
                "names": parameters_data1['names'],
539
                "timestamps": parameters_data1['timestamps'],
540
                "values": parameters_data1['values']
541
            },
542
            "virtualmeter2": {
543
                "name": virtual_meter2['name'],
544
                "energy_category_id": virtual_meter2['energy_category_id'],
545
                "energy_category_name": virtual_meter2['energy_category_name'],
546
                "unit_of_measure": virtual_meter2['unit_of_measure'],
547
                "equation": virtual_meter2['equation'] 
548
            },
549
            "reporting_period2": {
550
                "total_in_category": reporting2['total_in_category'],
551
                "timestamps": reporting2['timestamps'],
552
                "values": reporting2['values'],
553
            },
554
            "parameters2": {
555
                "names": parameters_data2['names'],
556
                "timestamps": parameters_data2['timestamps'],
557
                "values": parameters_data2['values']
558
            },
559
            "diff": {
560
                "values": diff['values'],
561
                "total_in_category": diff['total_in_category'],
562
            }
563
        }
564
        # export result to Excel file and then encode the file to base64 string
565
        if not is_quick_mode:
566
            result['excel_bytes_base64'] = \
567
                excelexporters.virtualmetercomparison.export(result,
568
                                                             virtual_meter1['name'],
569
                                                             virtual_meter2['name'],
570
                                                             reporting_period_start_datetime_local,
571
                                                             reporting_period_end_datetime_local,
572
                                                             period_type,
573
                                                             language)
574
575
        resp.text = json.dumps(result)
576