reports.virtualmeterbatch.Reporting.__init__()   A
last analyzed

Complexity

Conditions 1

Size

Total Lines 3
Code Lines 2

Duplication

Lines 3
Ratio 100 %

Importance

Changes 0
Metric Value
eloc 2
dl 3
loc 3
rs 10
c 0
b 0
f 0
cc 1
nop 1
1
"""
2
Virtual Meter Batch Report API
3
4
This module provides REST API endpoints for generating virtual meter batch reports.
5
It analyzes energy consumption data for multiple virtual meters within a space hierarchy,
6
providing comprehensive insights into virtual meter performance and energy usage patterns.
7
8
Key Features:
9
- Multi-virtual meter energy consumption analysis
10
- Space hierarchy traversal and analysis
11
- Energy category breakdown
12
- Base period vs reporting period comparison
13
- Excel export functionality
14
- Performance metrics calculation
15
16
Report Components:
17
- Virtual meter energy consumption summary
18
- Space-based virtual meter grouping
19
- Energy category analysis
20
- Performance comparison metrics
21
- Consumption trends and patterns
22
- Efficiency indicators
23
24
The module uses Falcon framework for REST API and includes:
25
- Database queries for virtual meter data
26
- Space tree traversal algorithms
27
- Energy consumption calculations
28
- Excel export via excelexporters
29
- Multi-language support
30
- User authentication and authorization
31
"""
32
33
from datetime import datetime, timedelta, timezone
34
import falcon
35
import mysql.connector
36
import simplejson as json
37
from anytree import AnyNode, LevelOrderIter
38
import config
39
import excelexporters.virtualmeterbatch
40
from core.useractivity import access_control, api_key_control
41
42
43 View Code Duplication
class Reporting:
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
44
    def __init__(self):
45
        """"Initializes Reporting"""
46
        pass
47
48
    @staticmethod
49
    def on_options(req, resp):
50
        _ = req
51
        resp.status = falcon.HTTP_200
52
53
    ####################################################################################################################
54
    # PROCEDURES
55
    # Step 1: valid parameters
56
    # Step 2: build a space tree
57
    # Step 3: query all virtual meters in the space tree
58
    # Step 4: query energy categories
59
    # Step 5: query reporting period energy input
60
    # Step 6: construct the report
61
    ####################################################################################################################
62
    @staticmethod
63
    def on_get(req, resp):
64
        if 'API-KEY' not in req.headers or \
65
                not isinstance(req.headers['API-KEY'], str) or \
66
                len(str.strip(req.headers['API-KEY'])) == 0:
67
            access_control(req)
68
        else:
69
            api_key_control(req)
70
        print(req.params)
71
        space_id = req.params.get('spaceid')
72
        reporting_period_start_datetime_local = req.params.get('reportingperiodstartdatetime')
73
        reporting_period_end_datetime_local = req.params.get('reportingperiodenddatetime')
74
        language = req.params.get('language')
75
        quick_mode = req.params.get('quickmode')
76
77
        ################################################################################################################
78
        # Step 1: valid parameters
79
        ################################################################################################################
80
        if space_id is None:
81
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST', description='API.INVALID_SPACE_ID')
82
        else:
83
            space_id = str.strip(space_id)
84
            if not space_id.isdigit() or int(space_id) <= 0:
85
                raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
86
                                       description='API.INVALID_SPACE_ID')
87
            else:
88
                space_id = int(space_id)
89
90
        timezone_offset = int(config.utc_offset[1:3]) * 60 + int(config.utc_offset[4:6])
91
        if config.utc_offset[0] == '-':
92
            timezone_offset = -timezone_offset
93
94
        if reporting_period_start_datetime_local is None:
95
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
96
                                   description="API.INVALID_REPORTING_PERIOD_START_DATETIME")
97
        else:
98
            reporting_period_start_datetime_local = str.strip(reporting_period_start_datetime_local)
99
            try:
100
                reporting_start_datetime_utc = datetime.strptime(reporting_period_start_datetime_local,
101
                                                                 '%Y-%m-%dT%H:%M:%S')
102
            except ValueError:
103
                raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
104
                                       description="API.INVALID_REPORTING_PERIOD_START_DATETIME")
105
            reporting_start_datetime_utc = \
106
                reporting_start_datetime_utc.replace(tzinfo=timezone.utc) - timedelta(minutes=timezone_offset)
107
            # nomalize the start datetime
108
            if config.minutes_to_count == 30 and reporting_start_datetime_utc.minute >= 30:
109
                reporting_start_datetime_utc = reporting_start_datetime_utc.replace(minute=30, second=0, microsecond=0)
110
            else:
111
                reporting_start_datetime_utc = reporting_start_datetime_utc.replace(minute=0, second=0, microsecond=0)
112
113
        if reporting_period_end_datetime_local is None:
114
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
115
                                   description="API.INVALID_REPORTING_PERIOD_END_DATETIME")
116
        else:
117
            reporting_period_end_datetime_local = str.strip(reporting_period_end_datetime_local)
118
            try:
119
                reporting_end_datetime_utc = datetime.strptime(reporting_period_end_datetime_local,
120
                                                               '%Y-%m-%dT%H:%M:%S')
121
            except ValueError:
122
                raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
123
                                       description="API.INVALID_REPORTING_PERIOD_END_DATETIME")
124
            reporting_end_datetime_utc = reporting_end_datetime_utc.replace(tzinfo=timezone.utc) - \
125
                timedelta(minutes=timezone_offset)
126
127
        if reporting_start_datetime_utc >= reporting_end_datetime_utc:
128
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
129
                                   description='API.INVALID_REPORTING_PERIOD_END_DATETIME')
130
131
        # if turn quick mode on, do not return parameters data and excel file
132
        is_quick_mode = False
133
        if quick_mode is not None and \
134
            len(str.strip(quick_mode)) > 0 and \
135
                str.lower(str.strip(quick_mode)) in ('true', 't', 'on', 'yes', 'y'):
136
            is_quick_mode = True
137
138
        cnx_system_db = mysql.connector.connect(**config.myems_system_db)
139
        cursor_system_db = cnx_system_db.cursor()
140
141
        cursor_system_db.execute(" SELECT name "
142
                                 " FROM tbl_spaces "
143
                                 " WHERE id = %s ", (space_id,))
144
        row = cursor_system_db.fetchone()
145
146
        if row is None:
147
            if cursor_system_db:
148
                cursor_system_db.close()
149
            if cnx_system_db:
150
                cnx_system_db.close()
151
            raise falcon.HTTPError(status=falcon.HTTP_404, title='API.NOT_FOUND',
152
                                   description='API.SPACE_NOT_FOUND')
153
        else:
154
            space_name = row[0]
155
156
        ################################################################################################################
157
        # Step 2: build a space tree
158
        ################################################################################################################
159
160
        query = (" SELECT id, name, parent_space_id "
161
                 " FROM tbl_spaces "
162
                 " ORDER BY id ")
163
        cursor_system_db.execute(query)
164
        rows_spaces = cursor_system_db.fetchall()
165
        node_dict = dict()
166
        if rows_spaces is not None and len(rows_spaces) > 0:
167
            for row in rows_spaces:
168
                parent_node = node_dict[row[2]] if row[2] is not None else None
169
                node_dict[row[0]] = AnyNode(id=row[0], parent=parent_node, name=row[1])
170
171
        ################################################################################################################
172
        # Step 3: query all meters in the space tree
173
        ################################################################################################################
174
        virtual_meter_dict = dict()
175
        space_dict = dict()
176
        energy_category_set = set()
177
178
        for node in LevelOrderIter(node_dict[space_id]):
179
            space_dict[node.id] = node.name
180
181
        cursor_system_db.execute(" SELECT vm.id, vm.name AS virtual_meter_name, vm.energy_category_id, "
182
                                 "        s.name AS space_name, "
183
                                 "        cc.name AS cost_center_name"
184
                                 " FROM tbl_spaces s, tbl_spaces_virtual_meters svm, "
185
                                 "      tbl_virtual_meters vm, tbl_cost_centers cc "
186
                                 " WHERE s.id IN ( " + ', '.join(map(str, space_dict.keys())) + ") "
187
                                 " AND svm.space_id = s.id AND svm.virtual_meter_id = vm.id "
188
                                 " AND vm.cost_center_id = cc.id  ", )
189
        rows_meters = cursor_system_db.fetchall()
190
        if rows_meters is not None and len(rows_meters) > 0:
191
            for row in rows_meters:
192
                virtual_meter_dict[row[0]] = {"virtual_meter_name": row[1],
193
                                              "energy_category_id": row[2],
194
                                              "space_name": row[3],
195
                                              "cost_center_name": row[4],
196
                                              "values": list(),
197
                                              "subtotal": None}
198
                energy_category_set.add(row[2])
199
200
        ################################################################################################################
201
        # Step 4: query energy categories
202
        ################################################################################################################
203
        cnx_energy_db = mysql.connector.connect(**config.myems_energy_db)
204
        cursor_energy_db = cnx_energy_db.cursor()
205
206
        # query all energy categories
207
        cursor_system_db.execute(" SELECT id, name, unit_of_measure "
208
                                 " FROM tbl_energy_categories "
209
                                 " ORDER BY id ", )
210
        rows_energy_categories = cursor_system_db.fetchall()
211
        if rows_energy_categories is None or len(rows_energy_categories) == 0:
212
            if cursor_system_db:
213
                cursor_system_db.close()
214
            if cnx_system_db:
215
                cnx_system_db.close()
216
217
            if cursor_energy_db:
218
                cursor_energy_db.close()
219
            if cnx_energy_db:
220
                cnx_energy_db.close()
221
222
            raise falcon.HTTPError(status=falcon.HTTP_404,
223
                                   title='API.NOT_FOUND',
224
                                   description='API.ENERGY_CATEGORY_NOT_FOUND')
225
        energy_category_list = list()
226
        for row_energy_category in rows_energy_categories:
227
            if row_energy_category[0] in energy_category_set:
228
                energy_category_list.append({"id": row_energy_category[0],
229
                                             "name": row_energy_category[1],
230
                                             "unit_of_measure": row_energy_category[2]})
231
232
        ################################################################################################################
233
        # Step 5: query reporting period energy input
234
        ################################################################################################################
235
        for virtual_meter_id in virtual_meter_dict:
236
237
            cursor_energy_db.execute(" SELECT SUM(actual_value) "
238
                                     " FROM tbl_virtual_meter_hourly "
239
                                     " WHERE virtual_meter_id = %s "
240
                                     "     AND start_datetime_utc >= %s "
241
                                     "     AND start_datetime_utc < %s ",
242
                                     (virtual_meter_id,
243
                                      reporting_start_datetime_utc,
244
                                      reporting_end_datetime_utc))
245
            rows_meter_energy = cursor_energy_db.fetchall()
246
            for energy_category in energy_category_list:
247
                subtotal = None
248
                for row_meter_energy in rows_meter_energy:
249
                    if energy_category['id'] == virtual_meter_dict[virtual_meter_id]['energy_category_id']:
250
                        subtotal = row_meter_energy[0]
251
                        virtual_meter_dict[virtual_meter_id]['subtotal'] = subtotal
252
                        break
253
                # append subtotal
254
                # append None if energy category is not applicable
255
                virtual_meter_dict[virtual_meter_id]['values'].append(subtotal)
256
257
        if cursor_system_db:
258
            cursor_system_db.close()
259
        if cnx_system_db:
260
            cnx_system_db.close()
261
262
        if cursor_energy_db:
263
            cursor_energy_db.close()
264
        if cnx_energy_db:
265
            cnx_energy_db.close()
266
267
        ################################################################################################################
268
        # Step 6: construct the report
269
        ################################################################################################################
270
        virtual_meter_list = list()
271
        for virtual_meter_id, virtual_meter in virtual_meter_dict.items():
272
            virtual_meter_list.append({
273
                "id": virtual_meter_id,
274
                "virtual_meter_name": virtual_meter['virtual_meter_name'],
275
                "space_name": virtual_meter['space_name'],
276
                "cost_center_name": virtual_meter['cost_center_name'],
277
                "values": virtual_meter['values'],
278
                "subtotal": virtual_meter['subtotal'],
279
            })
280
281
        result = {'virtual_meters': virtual_meter_list,
282
                  'energycategories': energy_category_list}
283
284
        # export result to Excel file and then encode the file to base64 string
285
        if not is_quick_mode:
286
            result['excel_bytes_base64'] = \
287
                excelexporters.virtualmeterbatch.export(result,
288
                                                        space_name,
289
                                                        reporting_period_start_datetime_local,
290
                                                        reporting_period_end_datetime_local,
291
                                                        language)
292
        resp.text = json.dumps(result)
293