diff --git a/README.md b/README.md index b7e0215b..758fac14 100644 --- a/README.md +++ b/README.md @@ -22,53 +22,43 @@ MyEMS是行业领先的开源能源管理系统,利用云计算、物联网、 ## MyEMS组件(社区版) MyEMS项目由下列组件构成: -### MyEMS 数据库 -SQL +### MyEMS 数据库 (SQL) [安装 数据库](./database/README.md) -### MyEMS API 应用程序接口 -Python +### MyEMS API 应用程序接口 (Python) [安装 myems-api](./myems-api/README.md) -### MyEMS 管理 UI -AngularJS +### MyEMS 管理 UI (AngularJS) [安装 admin UI](./admin/README.md) -### MyEMS BACnet/IP 数据采集服务 -Python +### MyEMS BACnet/IP 数据采集服务 (Python) [安装 myems-bacnet](./myems-bacnet/README.md) -### MyEMS Modbus TCP 数据采集服务 -Python +### MyEMS Modbus TCP 数据采集服务 (Python) [安装 myems-modbus-tcp](./myems-modbus-tcp/README.md) -### MyEMS MQTT数据转发服务 -Python +### MyEMS MQTT数据转发服务 (Python) [安装 myems-mqtt-publisher](./myems-mqtt-publisher/README.md) -### MyEMS 数据清洗服务 -Python +### MyEMS 数据清洗服务 (Python) [安装 myems-cleaning](./myems-cleaning/README.md) -### MyEMS 数据规范化服务 -Python +### MyEMS 数据规范化服务 (Python) [安装 myems-normalization](./myems-normalization/README.md) -### MyEMS 数据汇总服务 -Python +### MyEMS 数据汇总服务 (Python) [安装 myems-aggregation](./myems-aggregation/README.md) -### MyEMS Web UI -ReactJS +### MyEMS Web UI (ReactJS) [安装 web UI](./web/README.md) diff --git a/README_DE.md b/README_DE.md index 73212bb5..b5509178 100644 --- a/README_DE.md +++ b/README_DE.md @@ -23,53 +23,43 @@ MyEMS wird von einem erfahrenen Entwicklungsteam entwickelt und gewartet, und de Dieses Projekt besteht aus folgenden Komponenten: -### MyEMS Database -SQL +### MyEMS Database (SQL) [Installieren database](./database/README.md) -### MyEMS API -Python +### MyEMS API (Python) [Installieren myems-api](./myems-api/README.md) -### MyEMS Admin UI -AngularJS +### MyEMS Admin UI (AngularJS) [Installieren admin UI](./admin/README.md) -### MyEMS BACnet/IP Acquisition Service -Python +### MyEMS BACnet/IP Acquisition Service (Python) [Installieren myems-bacnet](./myems-bacnet/README.md) -### MyEMS Modbus TCP Acquisition Service -Python +### MyEMS Modbus TCP Acquisition Service (Python) [Installieren myems-modbus-tcp](./myems-modbus-tcp/README.md) -### MyEMS MQTT Data vorwärts Service -Python +### MyEMS MQTT Data vorwärts Service (Python) [Installieren myems-mqtt-publisher](./myems-mqtt-publisher/README.md) -### MyEMS Cleaning Service -Python +### MyEMS Cleaning Service (Python) [Installieren myems-cleaning](./myems-cleaning/README.md) -### MyEMS Normalization Service -Python +### MyEMS Normalization Service (Python) [Installieren myems-normalization](./myems-normalization/README.md) -### MyEMS Aggregation Service -Python +### MyEMS Aggregation Service (Python) [Installieren myems-aggregation](./myems-aggregation/README.md) -### MyEMS Web UI -ReactJS +### MyEMS Web UI (ReactJS) [Installieren web UI](./web/README.md) diff --git a/README_EN.md b/README_EN.md index 86bffdb8..1f43377d 100644 --- a/README_EN.md +++ b/README_EN.md @@ -23,53 +23,43 @@ MyEMS is being developed and maintained by an experienced development team, and This project is compose of following components: -### MyEMS Database -SQL +### MyEMS Database (SQL) [Install database](./database/README.md) -### MyEMS API -Python +### MyEMS API (Python) [Install myems-api](./myems-api/README.md) -### MyEMS Admin UI -ReactJS +### MyEMS Admin UI (ReactJS) [Install admin UI](./admin/README.md) -### MyEMS BACnet/IP Acquisition Service -Python +### MyEMS BACnet/IP Acquisition Service (Python) [Install myems-bacnet](./myems-bacnet/README.md) -### MyEMS Modbus TCP Acquisition Service -Python +### MyEMS Modbus TCP Acquisition Service (Python) [Install myems-modbus-tcp](./myems-modbus-tcp/README.md) -### MyEMS MQTT Data Forwarding Service -Python +### MyEMS MQTT Data Forwarding Service (Python) [Install myems-mqtt-publisher](./myems-mqtt-publisher/README.md) -### MyEMS Cleaning Service -Python +### MyEMS Cleaning Service (Python) [Install myems-cleaning](./myems-cleaning/README.md) -### MyEMS Normalization Service -Python +### MyEMS Normalization Service (Python) [Install myems-normalization](./myems-normalization/README.md) -### MyEMS Aggregation Service -Python +### MyEMS Aggregation Service (Python) [Install myems-aggregation](./myems-aggregation/README.md) -### MyEMS Web UI -AngularJS +### MyEMS Web UI (AngularJS) [Install web UI](./web/README.md) diff --git a/database/myems_fdd_db.sql b/database/myems_fdd_db.sql index d6d1dd17..aad4fdda 100644 --- a/database/myems_fdd_db.sql +++ b/database/myems_fdd_db.sql @@ -84,6 +84,24 @@ CREATE TABLE IF NOT EXISTS `myems_fdd_db`.`tbl_rules` ( PRIMARY KEY (`id`)); CREATE INDEX `tbl_rules_index_1` ON `myems_fdd_db`.`tbl_rules` (`name`); +-- --------------------------------------------------------------------------------------------------------------------- +-- Data for table `myems_fdd_db`.`tbl_rules` +-- --------------------------------------------------------------------------------------------------------------------- +-- INSERT INTO `tbl_rules` VALUES (1, '网关通讯异常报警', '31408ce8-f2ac-48bd-99c3-0466c746262b', 'SYSTEM01', 'SYSTEM', 'MEDIUM', 'EMAIL', '{\"minutes\": 30, \"recipients\": [{\"name\": \"Johnson\", \"email\": \"johnson@myems.io\", \"mobile\": \"8613888888888\"}], \"not_alarm_gateway\": []}', '$s1', 1); +-- INSERT INTO `tbl_rules` VALUES (2, '数据点通讯异常报警', '1350d71f-64d0-4276-835b-bfdf84a86bab', 'SYSTEM02', 'SYSTEM', 'MEDIUM', 'EMAIL', '{\"minutes\": 30, \"recipients\": [{\"name\": \"Johnson\", \"email\": \"johnson@myems.io\", \"mobile\": \"8613888888888\"}], \"not_alarm_points\": []}', '$s1', 1); +-- INSERT INTO `tbl_rules` VALUES (3, '电压电流异常报警', '947fdbfd-7915-4484-93bc-9dfe0c8622a8', 'REALTIME01', 'REALTIME', 'MEDIUM', 'EMAIL', '{\"minutes\": 30, \"recipients\": [{\"name\": \"Johnson\", \"email\": \"johnson@myems.io\", \"mobile\": \"8613888888888\"}], \"not_alarm_points\": []}', '$s1', 1); +-- INSERT INTO `tbl_rules` VALUES (4, '数据上下限异常报警', '0f9afcad-4fbd-4978-a4ed-5bc7c85ce04e', 'SPACE01', 'SPACE', 'MEDIUM', 'EMAIL', '{\"days\": 1, \"space\": {\"name\": \"远洋太古里\", \"limit\": [[\"电\", 60, 0], [\"水\", 999, 0], [\"天然气\", 999, 0]]}, \"period\": \"day\", \"recipients\": [{\"name\": \"Johnson\", \"email\": \"johnson@myems.io\", \"mobile\": \"8613888888888\"}]}', '$s1', 1); +-- INSERT INTO `tbl_rules` VALUES (5, '数据每日环比异常报警', '6f9c8f79-6495-47cb-b06f-f1d2cc767ee7', 'SPACE02', 'SPACE', 'MEDIUM', 'EMAIL', '{\"days\": 1, \"space\": {\"name\": \"远洋太古里\", \"limit\": [[\"电\", 0.6, 0.0], [\"水\", 0.99, 0.0], [\"天然气\", 0.99, 0.0]]}, \"period\": \"day\", \"recipients\": [{\"name\": \"Johnson\", \"email\": \"johnson@myems.io\", \"mobile\": \"8613888888888\"}]}', '$s1', 1); +-- INSERT INTO `tbl_rules` VALUES (6, '数据每周环比异常报警', 'e3cb0824-f0fb-4563-8b5f-00b610da6b15', 'SPACE03', 'SPACE', 'MEDIUM', 'EMAIL', '{\"days\": 7, \"space\": {\"name\": \"远洋太古里\", \"limit\": [[\"电\", 0.6, 0.0], [\"水\", 0.99, 0.0], [\"天然气\", 0.99, 0.0]]}, \"period\": \"week\", \"recipients\": [{\"name\": \"Johnson\", \"email\": \"johnson@myems.io\", \"mobile\": \"8613888888888\"}]}', '$s1', 1); +-- INSERT INTO `tbl_rules` VALUES (7, '数据每月环比异常报警', '005e92e7-380c-46f6-a543-7937d703d038', 'SPACE04', 'SPACE', 'MEDIUM', 'EMAIL', '{\"space\": {\"name\": \"远洋太古里\", \"limit\": [[\"电\", 0.6, 0.0], [\"水\", 0.99, 0.0], [\"天然气\", 0.99, 0.0]]}, \"months\": 1, \"period\": \"month\", \"recipients\": [{\"name\": \"Johnson\", \"email\": \"johnson@myems.io\", \"mobile\": \"8613888888888\"}]}', '$s1', 1); +-- INSERT INTO `tbl_rules` VALUES (8, '数据上下限异常报警', '5c201b7c-68fd-4c86-bc50-e44ee3cf29cb', 'METER01', 'METER', 'MEDIUM', 'EMAIL', '{\"days\": 1, \"meter\": {\"name\": \"低压柜主进线#1\", \"limit\": [999, 0]}, \"period\": \"day\", \"recipients\": [{\"name\": \"Johnson\", \"email\": \"johnson@myems.io\", \"mobile\": \"8613888888888\"}]}', '$s1', 1); +-- INSERT INTO `tbl_rules` VALUES (9, '数据每日环比异常报警', 'aa3a764a-e777-4a12-ac7a-89425cdff876', 'METER02', 'METER', 'MEDIUM', 'EMAIL', '{\"days\": 1, \"meter\": {\"name\": \"低压柜主进线#1\", \"limit\": [0.99, 0]}, \"period\": \"day\", \"recipients\": [{\"name\": \"Johnson\", \"email\": \"johnson@myems.io\", \"mobile\": \"8613888888888\"}]}', '$s1', 1); +-- INSERT INTO `tbl_rules` VALUES (10, '数据每周环比异常报警', '1b7dd8bf-904f-417c-9552-3e9085e06b12', 'METER03', 'METER', 'MEDIUM', 'EMAIL', '{\"days\": 7, \"meter\": {\"name\": \"低压柜主进线#1\", \"limit\": [0.99, 0]}, \"period\": \"week\", \"recipients\": [{\"name\": \"Johnson\", \"email\": \"johnson@myems.io\", \"mobile\": \"8613888888888\"}]}', '$s1', 1); +-- INSERT INTO `tbl_rules` VALUES (11, '数据每月环比异常报警', '8f5f1109-13b9-4b17-9b1d-ada6fa09d735', 'METER04', 'METER', 'MEDIUM', 'EMAIL', '{\"meter\": {\"name\": \"低压柜主进线#1\", \"limit\": [0.99, 0]}, \"months\": 1, \"period\": \"month\", \"recipients\": [{\"name\": \"Johnson\", \"email\": \"johnson@myems.io\", \"mobile\": \"8613888888888\"}]}', '$s1', 1); +-- INSERT INTO `tbl_rules` VALUES (12, '数据上下限异常报警', '7a033366-72e9-41a8-9660-9f4461b6d9fb', 'TENANT01', 'TENANT', 'MEDIUM', 'EMAIL', '{\"days\": 1, \"period\": \"day\", \"tenant\": {\"name\": \"Starbucks星巴克\", \"limit\": [[\"电\", 60, 0], [\"水\", 999, 0], [\"天然气\", 999, 0]]}, \"recipients\": [{\"name\": \"Johnson\", \"email\": \"johnson@myems.io\", \"mobile\": \"8613888888888\"}]}', '$s1', 1); +-- INSERT INTO `tbl_rules` VALUES (13, '数据每日环比异常报警', '7eb7ec13-87d2-44de-8070-d6d7ad933927', 'TENANT02', 'TENANT', 'MEDIUM', 'EMAIL', '{\"days\": 1, \"period\": \"day\", \"tenant\": {\"name\": \"Starbucks星巴克\", \"limit\": [[\"电\", 0.6, 0], [\"水\", 0.99, 0], [\"天然气\", 0.99, 0]]}, \"recipients\": [{\"name\": \"Johnson\", \"email\": \"johnson@myems.io\", \"mobile\": \"8613888888888\"}]}', '$s1', 1); +-- INSERT INTO `tbl_rules` VALUES (14, '数据每周环比异常报警', 'c33d7123-95f7-445c-a178-13a2b0d54489', 'TENANT03', 'TENANT', 'MEDIUM', 'EMAIL', '{\"days\": 7, \"period\": \"week\", \"tenant\": {\"name\": \"Starbucks星巴克\", \"limit\": [[\"电\", 0.6, 0], [\"水\", 0.99, 0], [\"天然气\", 0.99, 0]]}, \"recipients\": [{\"name\": \"Johnson\", \"email\": \"johnson@myems.io\", \"mobile\": \"8613888888888\"}]}', '$s1', 1); +-- INSERT INTO `tbl_rules` VALUES (15, '数据每月环比异常报警', '49183491-77f2-48dd-a56d-bd73e5971e1c', 'TENANT04', 'TENANT', 'MEDIUM', 'EMAIL', '{\"months\": 1, \"period\": \"month\", \"tenant\": {\"name\": \"Starbucks星巴克\", \"limit\": [[\"电\", 0.6, 0], [\"水\", 0.99, 0], [\"天然气\", 0.99, 0]]}, \"recipients\": [{\"name\": \"Johnson\", \"email\": \"johnson@myems.io\", \"mobile\": \"8613888888888\"}]}', '$s1', 1); INSERT INTO `tbl_rules` VALUES (1, '网关通讯异常报警', '1', 'SYSTEM01', 'SYSTEM', 'MEDIUM', 'EMAIL', '{\"minutes\": 30, \"recipients\": [{\"name\": \"Yinghao Huang\", \"email\": \"yinghao.huang@myems.io\", \"mobile\": \"8615652962029\"}], \"not_alarm_gateway\": []}', '$s1', 1, NULL, NULL); INSERT INTO `tbl_rules` VALUES (2, '数据点通讯异常报警', '2', 'SYSTEM02', 'SYSTEM', 'MEDIUM', 'EMAIL', '{\"minutes\": 30, \"recipients\": [{\"name\": \"Yinghao Huang\", \"email\": \"yinghao.huang@myems.io\", \"mobile\": \"8615652962029\"}], \"not_alarm_points\": []}', '$s1', 1, NULL, NULL); diff --git a/myems-api/core/utilities.py b/myems-api/core/utilities.py index 9d66d12b..2d070a66 100644 --- a/myems-api/core/utilities.py +++ b/myems-api/core/utilities.py @@ -16,6 +16,12 @@ import statistics ######################################################################################################################## def aggregate_hourly_data_by_period(rows_hourly, start_datetime_utc, end_datetime_utc, period_type): # todo: validate parameters + if start_datetime_utc is None or \ + end_datetime_utc is None or \ + start_datetime_utc >= end_datetime_utc or \ + period_type not in ('hourly', 'daily', 'monthly', 'yearly'): + return list() + start_datetime_utc = start_datetime_utc.replace(tzinfo=None) end_datetime_utc = end_datetime_utc.replace(tzinfo=None) diff --git a/myems-api/excelexporters/combinedequipmentefficiency.py b/myems-api/excelexporters/combinedequipmentefficiency.py index d91e620d..c7698950 100644 --- a/myems-api/excelexporters/combinedequipmentefficiency.py +++ b/myems-api/excelexporters/combinedequipmentefficiency.py @@ -484,17 +484,19 @@ def generate_excel(report, parameters_ws_current_row_number += 1 - table_current_col_number = 'B' + table_current_col_number = 2 for i in range(0, parameters_names_len): if len(parameters_data['timestamps'][i]) == 0: continue - parameters_ws[table_current_col_number + str(parameters_ws_current_row_number-1)].fill = table_fill - parameters_ws[table_current_col_number + str(parameters_ws_current_row_number-1)].border = f_border + col = format_cell.get_column_letter(table_current_col_number) - col = chr(ord(table_current_col_number) + 1) + parameters_ws[col + str(parameters_ws_current_row_number-1)].fill = table_fill + parameters_ws[col + str(parameters_ws_current_row_number-1)].border = f_border + + col = format_cell.get_column_letter(table_current_col_number + 1) parameters_ws[col + str(parameters_ws_current_row_number-1)].fill = table_fill parameters_ws[col + str(parameters_ws_current_row_number-1)].border = f_border @@ -505,14 +507,14 @@ def generate_excel(report, table_current_row_number = parameters_ws_current_row_number for j, value in enumerate(list(parameters_data['timestamps'][i])): - col = table_current_col_number + col = format_cell.get_column_letter(table_current_col_number) parameters_ws[col + str(table_current_row_number)].border = f_border parameters_ws[col + str(table_current_row_number)].font = title_font parameters_ws[col + str(table_current_row_number)].alignment = c_c_alignment parameters_ws[col + str(table_current_row_number)] = value - col = chr(ord(col) + 1) + col = format_cell.get_column_letter(table_current_col_number + 1) parameters_ws[col + str(table_current_row_number)].border = f_border parameters_ws[col + str(table_current_row_number)].font = title_font @@ -521,7 +523,7 @@ def generate_excel(report, table_current_row_number += 1 - table_current_col_number = chr(ord(table_current_col_number) + 3) + table_current_col_number = table_current_col_number + 3 ######################################################## # parameters chart and parameters table diff --git a/myems-api/excelexporters/combinedequipmentenergyitem.py b/myems-api/excelexporters/combinedequipmentenergyitem.py index ece85f12..690dd4d7 100644 --- a/myems-api/excelexporters/combinedequipmentenergyitem.py +++ b/myems-api/excelexporters/combinedequipmentenergyitem.py @@ -4,7 +4,6 @@ import os from openpyxl.chart import ( PieChart, LineChart, - BarChart, Reference, ) from openpyxl.styles import PatternFill, Border, Side, Alignment, Font @@ -432,6 +431,63 @@ def generate_excel(report, chart_start_row_number += 6 ws.add_chart(line, chart_cell) + ##################################### + + has_associated_equipment_flag = True + + if "associated_equipment" not in report.keys() or \ + "energy_item_names" not in report['associated_equipment'].keys() or \ + len(report['associated_equipment']["energy_item_names"]) == 0 \ + or 'associated_equipment_names_array' not in report['associated_equipment'].keys() \ + or report['associated_equipment']['associated_equipment_names_array'] is None \ + or len(report['associated_equipment']['associated_equipment_names_array']) == 0 \ + or len(report['associated_equipment']['associated_equipment_names_array'][0]) == 0: + has_associated_equipment_flag = False + + if has_associated_equipment_flag: + associated_equipment = report['associated_equipment'] + + ws['B' + str(current_row_number)].font = title_font + ws['B' + str(current_row_number)] = name + ' 相关设备数据' + + current_row_number += 1 + table_start_row_number = current_row_number + + ws.row_dimensions[current_row_number].height = 60 + ws['B' + str(current_row_number)].fill = table_fill + ws['B' + str(current_row_number)].font = name_font + ws['B' + str(current_row_number)].alignment = c_c_alignment + ws['B' + str(current_row_number)].border = f_border + ws['B' + str(current_row_number)] = '相关设备' + ca_len = len(associated_equipment['energy_item_names']) + + for i in range(0, ca_len): + row = chr(ord('C') + i) + ws[row + str(current_row_number)].fill = table_fill + ws[row + str(current_row_number)].font = name_font + ws[row + str(current_row_number)].alignment = c_c_alignment + ws[row + str(current_row_number)].border = f_border + ws[row + str(current_row_number)] = \ + reporting_period_data['names'][i] + " (" + reporting_period_data['units'][i] + ")" + + associated_equipment_len = len(associated_equipment['associated_equipment_names_array'][0]) + + for i in range(0, associated_equipment_len): + current_row_number += 1 + row = str(current_row_number) + + ws['B' + row].font = title_font + ws['B' + row].alignment = c_c_alignment + ws['B' + row] = associated_equipment['associated_equipment_names_array'][0][i] + ws['B' + row].border = f_border + + for j in range(0, ca_len): + col = chr(ord('C') + j) + ws[col + row].font = name_font + ws[col + row].alignment = c_c_alignment + ws[col + row] = round(associated_equipment['subtotals_array'][j][i], 2) + ws[col + row].border = f_border + filename = str(uuid.uuid4()) + '.xlsx' wb.save(filename) diff --git a/myems-api/excelexporters/equipmentcost.py b/myems-api/excelexporters/equipmentcost.py new file mode 100644 index 00000000..9dc4cfd5 --- /dev/null +++ b/myems-api/excelexporters/equipmentcost.py @@ -0,0 +1,735 @@ +import base64 +import uuid +import os +from openpyxl.chart import ( + PieChart, + LineChart, + BarChart, + Reference, + ) +from openpyxl.styles import PatternFill, Border, Side, Alignment, Font +from openpyxl.drawing.image import Image +from openpyxl import Workbook +from openpyxl.chart.label import DataLabelList +import openpyxl.utils.cell as format_cell + + +#################################################################################################################### +# PROCEDURES +# Step 1: Validate the report data +# Step 2: Generate excel file +# Step 3: Encode the excel file bytes to Base64 +#################################################################################################################### + + +def export(report, + name, + reporting_start_datetime_local, + reporting_end_datetime_local, + period_type): + #################################################################################################################### + # Step 1: Validate the report data + #################################################################################################################### + if report is None: + return None + print(report) + + #################################################################################################################### + # Step 2: Generate excel file from the report data + #################################################################################################################### + filename = generate_excel(report, + name, + reporting_start_datetime_local, + reporting_end_datetime_local, + period_type) + #################################################################################################################### + # Step 3: Encode the excel file to Base64 + #################################################################################################################### + try: + with open(filename, 'rb') as binary_file: + binary_file_data = binary_file.read() + except IOError as ex: + pass + + # Base64 encode the bytes + base64_encoded_data = base64.b64encode(binary_file_data) + # get the Base64 encoded data using human-readable characters. + base64_message = base64_encoded_data.decode('utf-8') + # delete the file from server + try: + os.remove(filename) + except NotImplementedError as ex: + pass + return base64_message + + +def generate_excel(report, + name, + reporting_start_datetime_local, + reporting_end_datetime_local, + period_type): + + wb = Workbook() + ws = wb.active + + # Row height + ws.row_dimensions[1].height = 102 + + for i in range(2, 2000 + 1): + ws.row_dimensions[i].height = 42 + + # Col width + ws.column_dimensions['A'].width = 1.5 + + ws.column_dimensions['B'].width = 25.0 + + for i in range(ord('C'), ord('L')): + ws.column_dimensions[chr(i)].width = 15.0 + + # Font + name_font = Font(name='Constantia', size=15, bold=True) + title_font = Font(name='宋体', size=15, bold=True) + data_font = Font(name='Franklin Gothic Book', size=11) + + table_fill = PatternFill(fill_type='solid', fgColor='1F497D') + f_border = Border(left=Side(border_style='medium', color='00000000'), + right=Side(border_style='medium', color='00000000'), + bottom=Side(border_style='medium', color='00000000'), + top=Side(border_style='medium', color='00000000') + ) + b_border = Border( + bottom=Side(border_style='medium', color='00000000'), + ) + + b_c_alignment = Alignment(vertical='bottom', + horizontal='center', + text_rotation=0, + wrap_text=True, + shrink_to_fit=False, + indent=0) + c_c_alignment = Alignment(vertical='center', + horizontal='center', + text_rotation=0, + wrap_text=True, + shrink_to_fit=False, + indent=0) + b_r_alignment = Alignment(vertical='bottom', + horizontal='right', + text_rotation=0, + wrap_text=True, + shrink_to_fit=False, + indent=0) + c_r_alignment = Alignment(vertical='bottom', + horizontal='center', + text_rotation=0, + wrap_text=True, + shrink_to_fit=False, + indent=0) + + # Img + img = Image("excelexporters/myems.png") + # img = Image("myems.png") + img.width = img.width * 1.06 + img.height = img.height * 1.06 + ws.add_image(img, 'B1') + + # Title + ws.row_dimensions[3].height = 60 + + ws['B3'].font = name_font + ws['B3'].alignment = b_r_alignment + ws['B3'] = 'Name:' + ws['C3'].border = b_border + ws['C3'].alignment = b_c_alignment + ws['C3'].font = name_font + ws['C3'] = name + + ws['D3'].font = name_font + ws['D3'].alignment = b_r_alignment + ws['D3'] = 'Period:' + ws['E3'].border = b_border + ws['E3'].alignment = b_c_alignment + ws['E3'].font = name_font + ws['E3'] = period_type + + ws['F3'].font = name_font + ws['F3'].alignment = b_r_alignment + ws['F3'] = 'Date:' + ws['G3'].border = b_border + ws['G3'].alignment = b_c_alignment + ws['G3'].font = name_font + ws['G3'] = reporting_start_datetime_local[:10] + "__" + reporting_end_datetime_local[:10] + ws.merge_cells("G3:H3") + + if "reporting_period" not in report.keys() or \ + "names" not in report['reporting_period'].keys() or len(report['reporting_period']['names']) == 0: + filename = str(uuid.uuid4()) + '.xlsx' + wb.save(filename) + + return filename + ################################################# + # First: 报告期成本分析 + # 6: title + # 7: table title + # 8~10 table_data + # Total: 5 rows + # if has not energy data: set low height for rows + ################################################# + reporting_period_data = report['reporting_period'] + + has_energy_data_flag = True + if "names" not in reporting_period_data.keys() or \ + reporting_period_data['names'] is None or \ + len(reporting_period_data['names']) == 0: + has_energy_data_flag = False + + if has_energy_data_flag: + ws['B6'].font = title_font + ws['B6'] = name+' 报告期成本分析' + + category = reporting_period_data['names'] + ca_len = len(category) + + ws.row_dimensions[7].height = 60 + ws['B7'].fill = table_fill + ws['B7'].border = f_border + + ws['B8'].font = title_font + ws['B8'].alignment = c_c_alignment + ws['B8'] = '成本' + ws['B8'].border = f_border + + ws['B9'].font = title_font + ws['B9'].alignment = c_c_alignment + ws['B9'] = '环比' + ws['B9'].border = f_border + + col = '' + + for i in range(0, ca_len): + col = chr(ord('C') + i) + row = '7' + cell = col + row + ws[col + '7'].fill = table_fill + ws[col + '7'].font = name_font + ws[col + '7'].alignment = c_c_alignment + ws[col + '7'] = reporting_period_data['names'][i] + " (" + reporting_period_data['units'][i] + ")" + ws[col + '7'].border = f_border + + ws[col + '8'].font = name_font + ws[col + '8'].alignment = c_c_alignment + ws[col + '8'] = round(reporting_period_data['subtotals'][i], 2) + ws[col + '8'].border = f_border + + ws[col + '9'].font = name_font + ws[col + '9'].alignment = c_c_alignment + ws[col + '9'] = str(round(reporting_period_data['increment_rates'][i] * 100, 2)) + "%" \ + if reporting_period_data['increment_rates'][i] is not None else "-" + ws[col + '9'].border = f_border + + col = chr(ord(col) + 1) + + ws[col + '7'].fill = table_fill + ws[col + '7'].font = name_font + ws[col + '7'].alignment = c_c_alignment + ws[col + '7'] = "总计 (" + reporting_period_data['total_unit'] + ")" + ws[col + '7'].border = f_border + + ws[col + '8'].font = name_font + ws[col + '8'].alignment = c_c_alignment + ws[col + '8'] = round(reporting_period_data['total'], 2) + ws[col + '8'].border = f_border + + ws[col + '9'].font = name_font + ws[col + '9'].alignment = c_c_alignment + ws[col + '9'] = str(round(reporting_period_data['total_increment_rate'] * 100, 2)) + "%" \ + if reporting_period_data['total_increment_rate'] is not None else "-" + ws[col + '9'].border = f_border + + else: + for i in range(6, 8 + 1): + ws.row_dimensions[i].height = 0.1 + ################################################# + # Second: 分时用电成本 + # 12: title + # 13: table title + # 14~17 table_data + # Total: 6 rows + ################################################ + has_ele_peak_flag = True + if "toppeaks" not in reporting_period_data.keys() or \ + reporting_period_data['toppeaks'] is None or \ + len(reporting_period_data['toppeaks']) == 0: + has_ele_peak_flag = False + + if has_ele_peak_flag: + ws['B12'].font = title_font + ws['B12'] = name+' 分时用电成本' + + ws.row_dimensions[13].height = 60 + ws['B13'].fill = table_fill + ws['B13'].font = name_font + ws['B13'].alignment = c_c_alignment + ws['B13'].border = f_border + + ws['C13'].fill = table_fill + ws['C13'].font = name_font + ws['C13'].alignment = c_c_alignment + ws['C13'].border = f_border + ws['C13'] = '分时用电成本' + + ws['B14'].font = title_font + ws['B14'].alignment = c_c_alignment + ws['B14'] = '尖' + ws['B14'].border = f_border + + ws['C14'].font = title_font + ws['C14'].alignment = c_c_alignment + ws['C14'].border = f_border + ws['C14'] = round(reporting_period_data['toppeaks'][0], 2) + + ws['B15'].font = title_font + ws['B15'].alignment = c_c_alignment + ws['B15'] = '峰' + ws['B15'].border = f_border + + ws['C15'].font = title_font + ws['C15'].alignment = c_c_alignment + ws['C15'].border = f_border + ws['C15'] = round(reporting_period_data['onpeaks'][0], 2) + + ws['B16'].font = title_font + ws['B16'].alignment = c_c_alignment + ws['B16'] = '平' + ws['B16'].border = f_border + + ws['C16'].font = title_font + ws['C16'].alignment = c_c_alignment + ws['C16'].border = f_border + ws['C16'] = round(reporting_period_data['midpeaks'][0], 2) + + ws['B17'].font = title_font + ws['B17'].alignment = c_c_alignment + ws['B17'] = '谷' + ws['B17'].border = f_border + + ws['C17'].font = title_font + ws['C17'].alignment = c_c_alignment + ws['C17'].border = f_border + ws['C17'] = round(reporting_period_data['offpeaks'][0], 2) + + pie = PieChart() + pie.title = name+' 分时用电成本' + labels = Reference(ws, min_col=2, min_row=14, max_row=17) + pie_data = Reference(ws, min_col=3, min_row=13, max_row=17) + pie.add_data(pie_data, titles_from_data=True) + pie.set_categories(labels) + pie.height = 7.25 # cm 1.05*5 1.05cm = 30 pt + pie.width = 9 + # pie.title = "Pies sold by category" + s1 = pie.series[0] + s1.dLbls = DataLabelList() + s1.dLbls.showCatName = False # 标签显示 + s1.dLbls.showVal = True # 数量显示 + s1.dLbls.showPercent = True # 百分比显示 + # s1 = CharacterProperties(sz=1800) # 图表中字体大小 *100 + + ws.add_chart(pie, "D13") + + else: + for i in range(12, 18 + 1): + ws.row_dimensions[i].height = 0.1 + # end_row 10 + # start_row 12 + ################################################ + # Second: 成本占比 + ################################################ + current_row_number = 19 + + has_subtotals_data_flag = True + + if 'subtotals' not in reporting_period_data.keys() or \ + reporting_period_data['subtotals'] is None: + has_subtotals_data_flag = False + + if has_subtotals_data_flag: + ws['B' + str(current_row_number)].font = title_font + ws['B' + str(current_row_number)] = name + ' 成本占比' + + current_row_number += 1 + table_start_row_number = current_row_number + + ws['B' + str(current_row_number)].fill = table_fill + ws['B' + str(current_row_number)].font = name_font + ws['B' + str(current_row_number)].alignment = c_c_alignment + ws['B' + str(current_row_number)].border = f_border + + ws['C' + str(current_row_number)].fill = table_fill + ws['C' + str(current_row_number)].font = name_font + ws['C' + str(current_row_number)].alignment = c_c_alignment + ws['C' + str(current_row_number)].border = f_border + ws['C' + str(current_row_number)] = '成本占比' + + current_row_number += 1 + + category = reporting_period_data['names'] + ca_len = len(category) + + for i in range(0, ca_len): + ws['B' + str(current_row_number)].font = title_font + ws['B' + str(current_row_number)].alignment = c_c_alignment + ws['B' + str(current_row_number)] = reporting_period_data['names'][i] + \ + ' (' + reporting_period_data['units'][i] + ')' + ws['B' + str(current_row_number)].border = f_border + + ws['C' + str(current_row_number)].font = title_font + ws['C' + str(current_row_number)].alignment = c_c_alignment + ws['C' + str(current_row_number)] = round(reporting_period_data['subtotals'][i], 3) + ws['C' + str(current_row_number)].border = f_border + + current_row_number += 1 + + table_end_row_number = current_row_number - 1 + + pie = PieChart() + pie.title = name + ' 成本占比' + labels = Reference(ws, min_col=2, min_row=table_start_row_number + 1, max_row=table_end_row_number) + pie_data = Reference(ws, min_col=3, min_row=table_start_row_number, max_row=table_end_row_number) + pie.add_data(pie_data, titles_from_data=True) + pie.set_categories(labels) + pie.height = 6.6 + pie.width = 9 + s1 = pie.series[0] + s1.dLbls = DataLabelList() + s1.dLbls.showCatName = False + s1.dLbls.showVal = True + s1.dLbls.showPercent = True + + ws.add_chart(pie, 'D' + str(table_start_row_number)) + + if ca_len < 4: + current_row_number = current_row_number - ca_len + 4 + + current_row_number += 1 + + ################################################ + # Fourth: 成本详情 + # current_row_number: title + # current_row_number+1 ~ current_row_number+1+ca_len*6-1: line + # current_row_number+1+ca_len*6: table title + # current_row_number+1+ca_len*6~: table_data + ################################################ + reporting_period_data = report['reporting_period'] + times = reporting_period_data['timestamps'] + has_detail_data_flag = True + ca_len = len(report['reporting_period']['names']) + real_timestamps_len = timestamps_data_not_equal_0(report['parameters']['timestamps']) + table_row = current_row_number + 2 + ca_len*6 + real_timestamps_len*7 + chart_start_row_number = current_row_number + 1 + if "timestamps" not in reporting_period_data.keys() or \ + reporting_period_data['timestamps'] is None or \ + len(reporting_period_data['timestamps']) == 0: + has_detail_data_flag = False + + if has_detail_data_flag: + ws['B' + str(current_row_number)].font = title_font + ws['B' + str(current_row_number)] = name+' 详细数据' + + ws.row_dimensions[table_row].height = 60 + ws['B'+str(table_row)].fill = table_fill + ws['B' + str(table_row)].font = title_font + ws['B'+str(table_row)].border = f_border + ws['B'+str(table_row)].alignment = c_c_alignment + ws['B'+str(table_row)] = '日期时间' + time = times[0] + has_data = False + max_row = 0 + if len(time) > 0: + has_data = True + max_row = table_row + len(time) + print("max_row", max_row) + + if has_data: + for i in range(0, len(time)): + col = 'B' + row = str(table_row+1 + i) + # col = chr(ord('B') + i) + ws[col + row].font = title_font + ws[col + row].alignment = c_c_alignment + ws[col + row] = time[i] + ws[col + row].border = f_border + + for i in range(0, ca_len): + # 38 title + col = chr(ord('C') + i) + + ws[col + str(table_row)].fill = table_fill + ws[col + str(table_row)].font = title_font + ws[col + str(table_row)].alignment = c_c_alignment + ws[col + str(table_row)] = reporting_period_data['names'][i] + \ + " (" + reporting_period_data['units'][i] + ")" + ws[col + str(table_row)].border = f_border + + # 39 data + time = times[i] + time_len = len(time) + + for j in range(0, time_len): + row = str(table_row+1 + j) + # col = chr(ord('B') + i) + ws[col + row].font = title_font + ws[col + row].alignment = c_c_alignment + ws[col + row] = round(reporting_period_data['values'][i][j], 2) + ws[col + row].border = f_border + + current_row_number = table_row + 1 + len(times[0]) + + ws['B' + str(current_row_number)].font = title_font + ws['B' + str(current_row_number)].alignment = c_c_alignment + ws['B' + str(current_row_number)].border = f_border + ws['B' + str(current_row_number)] = '小计' + + for i in range(0, ca_len): + col = chr(ord('C') + i) + ws[col + str(current_row_number)].font = title_font + ws[col + str(current_row_number)].alignment = c_c_alignment + ws[col + str(current_row_number)].border = f_border + ws[col + str(current_row_number)] = round(reporting_period_data['subtotals'][i], 2) + + # line + # 39~: line + line = LineChart() + line.title = '报告期消耗 - ' + ws.cell(column=3+i, row=table_row).value + labels = Reference(ws, min_col=2, min_row=table_row+1, max_row=max_row) + line_data = Reference(ws, min_col=3 + i, min_row=table_row, max_row=max_row) # openpyxl bug + line.add_data(line_data, titles_from_data=True) + line.set_categories(labels) + line_data = line.series[0] + line_data.marker.symbol = "circle" + line_data.smooth = True + line.x_axis.crosses = 'min' + line.height = 8.25 # cm 1.05*5 1.05cm = 30 pt + line.width = 24 + # pie.title = "Pies sold by category" + line.dLbls = DataLabelList() + line.dLbls.dLblPos = 't' + # line.dLbls.showCatName = True # label show + line.dLbls.showVal = True # val show + line.dLbls.showPercent = True # percent show + # s1 = CharacterProperties(sz=1800) # font size *100 + chart_col = 'B' + chart_cell = chart_col + str(chart_start_row_number + 6*i) + ws.add_chart(line, chart_cell) + + current_sheet_parameters_row_number = chart_start_row_number + ca_len * 6 + ########################################## + has_parameters_names_and_timestamps_and_values_data = True + if 'parameters' not in report.keys() or \ + report['parameters'] is None or \ + 'names' not in report['parameters'].keys() or \ + report['parameters']['names'] is None or \ + len(report['parameters']['names']) == 0 or \ + 'timestamps' not in report['parameters'].keys() or \ + report['parameters']['timestamps'] is None or \ + len(report['parameters']['timestamps']) == 0 or \ + 'values' not in report['parameters'].keys() or \ + report['parameters']['values'] is None or \ + len(report['parameters']['values']) == 0 or \ + timestamps_data_all_equal_0(report['parameters']['timestamps']): + + has_parameters_names_and_timestamps_and_values_data = False + if has_parameters_names_and_timestamps_and_values_data: + + ############################### + # new worksheet + ############################### + + parameters_data = report['parameters'] + parameters_names_len = len(parameters_data['names']) + + parameters_ws = wb.create_sheet('相关参数') + + parameters_timestamps_data_max_len = \ + get_parameters_timestamps_lists_max_len(list(parameters_data['timestamps'])) + + # Row height + parameters_ws.row_dimensions[1].height = 102 + for i in range(2, 7 + 1): + parameters_ws.row_dimensions[i].height = 42 + + for i in range(8, parameters_timestamps_data_max_len + 10): + parameters_ws.row_dimensions[i].height = 60 + + # Col width + parameters_ws.column_dimensions['A'].width = 1.5 + + parameters_ws.column_dimensions['B'].width = 25.0 + + for i in range(3, 12+parameters_names_len*3): + parameters_ws.column_dimensions[format_cell.get_column_letter(i)].width = 15.0 + + # Img + img = Image("excelexporters/myems.png") + img.width = img.width * 0.85 + img.height = img.height * 0.85 + # img = Image("myems.png") + parameters_ws.add_image(img, 'B1') + + # Title + parameters_ws.row_dimensions[3].height = 60 + + parameters_ws['B3'].font = name_font + parameters_ws['B3'].alignment = b_r_alignment + parameters_ws['B3'] = 'Name:' + parameters_ws['C3'].border = b_border + parameters_ws['C3'].alignment = b_c_alignment + parameters_ws['C3'].font = name_font + parameters_ws['C3'] = name + + parameters_ws['D3'].font = name_font + parameters_ws['D3'].alignment = b_r_alignment + parameters_ws['D3'] = 'Period:' + parameters_ws['E3'].border = b_border + parameters_ws['E3'].alignment = b_c_alignment + parameters_ws['E3'].font = name_font + parameters_ws['E3'] = period_type + + parameters_ws['F3'].font = name_font + parameters_ws['F3'].alignment = b_r_alignment + parameters_ws['F3'] = 'Date:' + parameters_ws['G3'].border = b_border + parameters_ws['G3'].alignment = b_c_alignment + parameters_ws['G3'].font = name_font + parameters_ws['G3'] = reporting_start_datetime_local + "__" + reporting_end_datetime_local + parameters_ws.merge_cells("G3:H3") + + parameters_ws_current_row_number = 6 + + parameters_ws['B' + str(parameters_ws_current_row_number)].font = title_font + parameters_ws['B' + str(parameters_ws_current_row_number)] = name + ' 相关参数' + + parameters_ws_current_row_number += 1 + + parameters_table_start_row_number = parameters_ws_current_row_number + + parameters_ws.row_dimensions[parameters_ws_current_row_number].height = 80 + + parameters_ws_current_row_number += 1 + + table_current_col_number = 'B' + + for i in range(0, parameters_names_len): + + if len(parameters_data['timestamps'][i]) == 0: + continue + + parameters_ws[table_current_col_number + str(parameters_ws_current_row_number-1)].fill = table_fill + parameters_ws[table_current_col_number + str(parameters_ws_current_row_number-1)].border = f_border + + col = chr(ord(table_current_col_number) + 1) + + parameters_ws[col + str(parameters_ws_current_row_number-1)].fill = table_fill + parameters_ws[col + str(parameters_ws_current_row_number-1)].border = f_border + parameters_ws[col + str(parameters_ws_current_row_number-1)].font = name_font + parameters_ws[col + str(parameters_ws_current_row_number-1)].alignment = c_c_alignment + parameters_ws[col + str(parameters_ws_current_row_number-1)] = parameters_data['names'][i] + + table_current_row_number = parameters_ws_current_row_number + + for j, value in enumerate(list(parameters_data['timestamps'][i])): + col = table_current_col_number + + parameters_ws[col + str(table_current_row_number)].border = f_border + parameters_ws[col + str(table_current_row_number)].font = title_font + parameters_ws[col + str(table_current_row_number)].alignment = c_c_alignment + parameters_ws[col + str(table_current_row_number)] = value + + col = chr(ord(col) + 1) + + parameters_ws[col + str(table_current_row_number)].border = f_border + parameters_ws[col + str(table_current_row_number)].font = title_font + parameters_ws[col + str(table_current_row_number)].alignment = c_c_alignment + parameters_ws[col + str(table_current_row_number)] = round(parameters_data['values'][i][j], 2) + + table_current_row_number += 1 + + table_current_col_number = chr(ord(table_current_col_number) + 3) + + ######################################################## + # parameters chart and parameters table + ######################################################## + + ws['B' + str(current_sheet_parameters_row_number)].font = title_font + ws['B' + str(current_sheet_parameters_row_number)] = name + ' 相关参数' + + current_sheet_parameters_row_number += 1 + + chart_start_row_number = current_sheet_parameters_row_number + + col_index = 0 + + for i in range(0, parameters_names_len): + + if len(parameters_data['timestamps'][i]) == 0: + continue + + line = LineChart() + data_col = 3+col_index*3 + labels_col = 2+col_index*3 + col_index += 1 + line.title = '相关参数 - ' + \ + parameters_ws.cell(row=parameters_table_start_row_number, column=data_col).value + labels = Reference(parameters_ws, min_col=labels_col, min_row=parameters_table_start_row_number + 1, + max_row=(len(parameters_data['timestamps'][i])+parameters_table_start_row_number)) + line_data = Reference(parameters_ws, min_col=data_col, min_row=parameters_table_start_row_number, + max_row=(len(parameters_data['timestamps'][i])+parameters_table_start_row_number)) + line.add_data(line_data, titles_from_data=True) + line.set_categories(labels) + line_data = line.series[0] + line_data.marker.symbol = "circle" + line_data.smooth = True + line.x_axis.crosses = 'min' + line.height = 8.25 + line.width = 24 + line.dLbls = DataLabelList() + line.dLbls.dLblPos = 't' + line.dLbls.showVal = False + line.dLbls.showPercent = False + chart_col = 'B' + chart_cell = chart_col + str(chart_start_row_number) + chart_start_row_number += 6 + ws.add_chart(line, chart_cell) + + current_sheet_parameters_row_number = chart_start_row_number + + current_sheet_parameters_row_number += 1 + ########################################## + filename = str(uuid.uuid4()) + '.xlsx' + wb.save(filename) + + return filename + + +def timestamps_data_all_equal_0(lists): + for i, value in enumerate(list(lists)): + if len(value) > 0: + return False + + return True + + +def get_parameters_timestamps_lists_max_len(parameters_timestamps_lists): + max_len = 0 + for i, value in enumerate(list(parameters_timestamps_lists)): + if len(value) > max_len: + max_len = len(value) + + return max_len + + +def timestamps_data_not_equal_0(lists): + number = 0 + for i, value in enumerate(list(lists)): + if len(value) > 0: + number += 1 + return number diff --git a/myems-api/excelexporters/equipmentefficiency.py b/myems-api/excelexporters/equipmentefficiency.py index d91e620d..18165e59 100644 --- a/myems-api/excelexporters/equipmentefficiency.py +++ b/myems-api/excelexporters/equipmentefficiency.py @@ -484,35 +484,37 @@ def generate_excel(report, parameters_ws_current_row_number += 1 - table_current_col_number = 'B' + table_current_col_number = 2 for i in range(0, parameters_names_len): if len(parameters_data['timestamps'][i]) == 0: continue - parameters_ws[table_current_col_number + str(parameters_ws_current_row_number-1)].fill = table_fill - parameters_ws[table_current_col_number + str(parameters_ws_current_row_number-1)].border = f_border + col = format_cell.get_column_letter(table_current_col_number) - col = chr(ord(table_current_col_number) + 1) + parameters_ws[col + str(parameters_ws_current_row_number - 1)].fill = table_fill + parameters_ws[col + str(parameters_ws_current_row_number - 1)].border = f_border - parameters_ws[col + str(parameters_ws_current_row_number-1)].fill = table_fill - parameters_ws[col + str(parameters_ws_current_row_number-1)].border = f_border - parameters_ws[col + str(parameters_ws_current_row_number-1)].font = name_font - parameters_ws[col + str(parameters_ws_current_row_number-1)].alignment = c_c_alignment - parameters_ws[col + str(parameters_ws_current_row_number-1)] = parameters_data['names'][i] + col = format_cell.get_column_letter(table_current_col_number + 1) + + parameters_ws[col + str(parameters_ws_current_row_number - 1)].fill = table_fill + parameters_ws[col + str(parameters_ws_current_row_number - 1)].border = f_border + parameters_ws[col + str(parameters_ws_current_row_number - 1)].font = name_font + parameters_ws[col + str(parameters_ws_current_row_number - 1)].alignment = c_c_alignment + parameters_ws[col + str(parameters_ws_current_row_number - 1)] = parameters_data['names'][i] table_current_row_number = parameters_ws_current_row_number for j, value in enumerate(list(parameters_data['timestamps'][i])): - col = table_current_col_number + col = format_cell.get_column_letter(table_current_col_number) parameters_ws[col + str(table_current_row_number)].border = f_border parameters_ws[col + str(table_current_row_number)].font = title_font parameters_ws[col + str(table_current_row_number)].alignment = c_c_alignment parameters_ws[col + str(table_current_row_number)] = value - col = chr(ord(col) + 1) + col = format_cell.get_column_letter(table_current_col_number + 1) parameters_ws[col + str(table_current_row_number)].border = f_border parameters_ws[col + str(table_current_row_number)].font = title_font @@ -521,7 +523,7 @@ def generate_excel(report, table_current_row_number += 1 - table_current_col_number = chr(ord(table_current_col_number) + 3) + table_current_col_number = table_current_col_number + 3 ######################################################## # parameters chart and parameters table diff --git a/myems-api/reports/combinedequipmentenergyitem.py b/myems-api/reports/combinedequipmentenergyitem.py index ced3add1..710376be 100644 --- a/myems-api/reports/combinedequipmentenergyitem.py +++ b/myems-api/reports/combinedequipmentenergyitem.py @@ -23,11 +23,13 @@ class Reporting: # Step 2: query the combined equipment # Step 3: query energy items # Step 4: query associated points - # Step 5: query base period energy input - # Step 6: query reporting period energy input - # Step 7: query tariff data - # Step 8: query associated points data - # Step 9: construct the report + # Step 5: query associated equipments + # Step 6: query base period energy input + # Step 7: query reporting period energy input + # Step 8: query tariff data + # Step 9: query associated points data + # Step 10: query associated equipments energy input + # Step 11: construct the report #################################################################################################################### @staticmethod def on_get(req, resp): @@ -239,7 +241,21 @@ class Reporting: point_list.append({"id": row[0], "name": row[1], "units": row[2], "object_type": row[3]}) ################################################################################################################ - # Step 5: query base period energy input + # Step 5: query associated equipments + ################################################################################################################ + associated_equipment_list = list() + cursor_system.execute(" SELECT e.id, e.name " + " FROM tbl_equipments e,tbl_combined_equipments_equipments ee" + " WHERE ee.combined_equipment_id = %s AND e.id = ee.equipment_id" + " ORDER BY e.id ", (combined_equipment['id'],)) + rows_associated_equipments = cursor_system.fetchall() + if rows_associated_equipments is not None and len(rows_associated_equipments) > 0: + for row in rows_associated_equipments: + associated_equipment_list.append({"id": row[0], "name": row[1]}) + + print(associated_equipment_list) + ################################################################################################################ + # Step 6: query base period energy input ################################################################################################################ base = dict() if energy_item_set is not None and len(energy_item_set) > 0: @@ -286,7 +302,7 @@ class Reporting: base[energy_item_id]['subtotal'] += actual_value ################################################################################################################ - # Step 6: query reporting period energy input + # Step 7: query reporting period energy input ################################################################################################################ reporting = dict() if energy_item_set is not None and len(energy_item_set) > 0: @@ -353,7 +369,7 @@ class Reporting: reporting[energy_item_id]['offpeak'] += row[1] ################################################################################################################ - # Step 7: query tariff data + # Step 8: query tariff data ################################################################################################################ parameters_data = dict() parameters_data['names'] = list() @@ -379,7 +395,7 @@ class Reporting: parameters_data['values'].append(tariff_value_list) ################################################################################################################ - # Step 8: query associated points data + # Step 9: query associated points data ################################################################################################################ for point in point_list: point_values = [] @@ -444,7 +460,37 @@ class Reporting: parameters_data['values'].append(point_values) ################################################################################################################ - # Step 9: construct the report + # Step 10: query associated equipments energy input + ################################################################################################################ + associated_equipment_data = dict() + + if energy_item_set is not None and len(energy_item_set) > 0: + for energy_item_id in energy_item_set: + associated_equipment_data[energy_item_id] = dict() + associated_equipment_data[energy_item_id]['associated_equipment_names'] = list() + associated_equipment_data[energy_item_id]['subtotals'] = list() + for associated_equipment in associated_equipment_list: + associated_equipment_data[energy_item_id]['associated_equipment_names'].append( + associated_equipment['name']) + + cursor_energy.execute(" SELECT SUM(actual_value) " + " FROM tbl_equipment_input_item_hourly " + " WHERE equipment_id = %s " + " AND energy_item_id = %s " + " AND start_datetime_utc >= %s " + " AND start_datetime_utc < %s " + " ORDER BY start_datetime_utc ", + (associated_equipment['id'], + energy_item_id, + reporting_start_datetime_utc, + reporting_end_datetime_utc)) + row_subtotal = cursor_energy.fetchone() + + subtotal = Decimal(0.0) if (row_subtotal is None or row_subtotal[0] is None) else row_subtotal[0] + associated_equipment_data[energy_item_id]['subtotals'].append(subtotal) + + ################################################################################################################ + # Step 11: construct the report ################################################################################################################ if cursor_system: cursor_system.close() @@ -522,6 +568,20 @@ class Reporting: "values": parameters_data['values'] } + result['associated_equipment'] = dict() + result['associated_equipment']['energy_item_names'] = list() + result['associated_equipment']['units'] = list() + result['associated_equipment']['associated_equipment_names_array'] = list() + result['associated_equipment']['subtotals_array'] = list() + if energy_item_set is not None and len(energy_item_set) > 0: + for energy_item_id in energy_item_set: + result['associated_equipment']['energy_item_names'].append(energy_item_dict[energy_item_id]['name']) + result['associated_equipment']['units'].append(energy_item_dict[energy_item_id]['unit_of_measure']) + result['associated_equipment']['associated_equipment_names_array'].append( + associated_equipment_data[energy_item_id]['associated_equipment_names']) + result['associated_equipment']['subtotals_array'].append( + associated_equipment_data[energy_item_id]['subtotals']) + # export result to Excel file and then encode the file to base64 string result['excel_bytes_base64'] = excelexporters.combinedequipmentenergyitem.export(result, combined_equipment['name'], diff --git a/myems-api/reports/equipmentcost.py b/myems-api/reports/equipmentcost.py index ed96fea5..3dea4973 100644 --- a/myems-api/reports/equipmentcost.py +++ b/myems-api/reports/equipmentcost.py @@ -5,6 +5,7 @@ import config from datetime import datetime, timedelta, timezone from core import utilities from decimal import Decimal +import excelexporters.equipmentcost class Reporting: @@ -511,5 +512,9 @@ class Reporting: "timestamps": parameters_data['timestamps'], "values": parameters_data['values'] } - + result['excel_bytes_base64'] = excelexporters.equipmentcost.export(result, + equipment['name'], + reporting_start_datetime_local, + reporting_end_datetime_local, + period_type) resp.body = json.dumps(result) diff --git a/myems-cleaning/clean_analog_value.py b/myems-cleaning/clean_analog_value.py index 2cba4254..73fe6fda 100644 --- a/myems-cleaning/clean_analog_value.py +++ b/myems-cleaning/clean_analog_value.py @@ -7,30 +7,32 @@ import schedule def job(logger): - cnx = None - cursor = None + cnx_historical = None + cursor_historical = None try: - cnx = mysql.connector.connect(**config.myems_historical_db) - cursor = cnx.cursor() + cnx_historical = mysql.connector.connect(**config.myems_historical_db) + cursor_historical = cnx_historical.cursor() except Exception as e: logger.error("Error in clean analog value process " + str(e)) - if cursor: - cursor.close() - if cnx: - cnx.close() + if cursor_historical: + cursor_historical.close() + if cnx_historical: + cnx_historical.disconnect() return expired_utc = datetime.utcnow() - timedelta(days=config.live_in_days) try: - cursor.execute(" DELETE FROM tbl_analog_value WHERE utc_date_time < %s ", (expired_utc,)) - cnx.commit() + cursor_historical.execute(" DELETE " + " FROM tbl_analog_value " + " WHERE utc_date_time < %s ", (expired_utc,)) + cnx_historical.commit() except Exception as e: logger.error("Error in delete_expired_trend process " + str(e)) finally: - if cursor: - cursor.close() - if cnx: - cnx.close() + if cursor_historical: + cursor_historical.close() + if cnx_historical: + cnx_historical.disconnect() logger.info("Deleted trend before date time in UTC: " + expired_utc.isoformat()[0:19]) diff --git a/myems-cleaning/clean_digital_value.py b/myems-cleaning/clean_digital_value.py index a3ebf631..47830009 100644 --- a/myems-cleaning/clean_digital_value.py +++ b/myems-cleaning/clean_digital_value.py @@ -7,30 +7,32 @@ import schedule def job(logger): - cnx = None - cursor = None + cnx_historical = None + cursor_historical = None try: - cnx = mysql.connector.connect(**config.myems_historical_db) - cursor = cnx.cursor() + cnx_historical = mysql.connector.connect(**config.myems_historical_db) + cursor_historical = cnx_historical.cursor() except Exception as e: logger.error("Error in clean digital value process " + str(e)) - if cursor: - cursor.close() - if cnx: - cnx.close() + if cursor_historical: + cursor_historical.close() + if cnx_historical: + cnx_historical.disconnect() return expired_utc = datetime.utcnow() - timedelta(days=config.live_in_days) try: - cursor.execute(" DELETE FROM tbl_digital_value WHERE utc_date_time < %s ", (expired_utc,)) - cnx.commit() + cursor_historical.execute(" DELETE " + " FROM tbl_digital_value " + " WHERE utc_date_time < %s ", (expired_utc,)) + cnx_historical.commit() except Exception as e: logger.error("Error in delete_expired_trend process " + str(e)) finally: - if cursor: - cursor.close() - if cnx: - cnx.close() + if cursor_historical: + cursor_historical.close() + if cnx_historical: + cnx_historical.disconnect() logger.info("Deleted trend before date time in UTC: " + expired_utc.isoformat()[0:19]) diff --git a/myems-cleaning/clean_energy_value.py b/myems-cleaning/clean_energy_value.py index 8a358da9..91792e59 100644 --- a/myems-cleaning/clean_energy_value.py +++ b/myems-cleaning/clean_energy_value.py @@ -1,7 +1,7 @@ import mysql.connector import config import time -from datetime import datetime +from datetime import datetime, timedelta ######################################################################################################################## @@ -17,17 +17,17 @@ def process(logger): while True: # the outermost loop to reconnect server if there is a connection error - cnx = None - cursor = None + cnx_historical = None + cursor_historical = None try: - cnx = mysql.connector.connect(**config.myems_historical_db) - cursor = cnx.cursor() + cnx_historical = mysql.connector.connect(**config.myems_historical_db) + cursor_historical = cnx_historical.cursor() except Exception as e: logger.error("Error at the begin of clean_energy_value.process " + str(e)) - if cursor: - cursor.close() - if cnx: - cnx.close() + if cursor_historical: + cursor_historical.close() + if cnx_historical: + cnx_historical.disconnect() time.sleep(60) continue @@ -46,19 +46,20 @@ def process(logger): query = (" SELECT MIN(utc_date_time), MAX(utc_date_time) " " FROM tbl_energy_value " " WHERE is_bad IS NULL ") - cursor.execute(query, ()) - row_datetime = cursor.fetchone() + cursor_historical.execute(query, ()) + row_datetime = cursor_historical.fetchone() if row_datetime is not None and len(row_datetime) == 2 and \ isinstance(row_datetime[0], datetime) and isinstance(row_datetime[1], datetime): - min_datetime = row_datetime[0] + # NOTE: To avoid omission mistakes, we start one hour early + min_datetime = row_datetime[0] - timedelta(hours=1) max_datetime = row_datetime[1] except Exception as e: logger.error("Error in Step 1 of clean_energy_value.process " + str(e)) - if cursor: - cursor.close() - if cnx: - cnx.close() + if cursor_historical: + cursor_historical.close() + if cnx_historical: + cnx_historical.disconnect() time.sleep(60) continue print("min_datetime: " + min_datetime.isoformat()[0:19]) @@ -170,6 +171,8 @@ def process(logger): # 3333 2018-02-08 00:54:16 165599.015625 good ################################################################################################################ print("Step 2: Processing bad case 1.x") + cnx_system = None + cursor_system = None try: cnx_system = mysql.connector.connect(**config.myems_system_db) cursor_system = cnx_system.cursor(dictionary=True) @@ -193,23 +196,24 @@ def process(logger): if cursor_system: cursor_system.close() if cnx_system: - cnx_system.close() + cnx_system.disconnect() try: query = (" SELECT id, point_id, actual_value " " FROM tbl_energy_value " " WHERE utc_date_time >= %s AND utc_date_time <= %s AND is_bad IS NOT TRUE ") - cursor.execute(query, (min_datetime, max_datetime,)) - rows_energy_values = cursor.fetchall() + cursor_historical.execute(query, (min_datetime, max_datetime,)) + rows_energy_values = cursor_historical.fetchall() except Exception as e: logger.error("Error in step 2.2 of clean_energy_value.process " + str(e)) - if cursor: - cursor.close() - if cnx: - cnx.close() + if cursor_historical: + cursor_historical.close() + if cnx_historical: + cnx_historical.disconnect() time.sleep(60) continue + # initialize bad list bad_list = list() if rows_energy_values is not None and len(rows_energy_values) > 0: @@ -226,14 +230,14 @@ def process(logger): update = (" UPDATE tbl_energy_value " " SET is_bad = TRUE " " WHERE id IN (" + ', '.join(map(str, bad_list)) + ")") - cursor.execute(update, ) - cnx.commit() + cursor_historical.execute(update, ) + cnx_historical.commit() except Exception as e: logger.error("Error in step 2.3 of clean_energy_value.process " + str(e)) - if cursor: - cursor.close() - if cnx: - cnx.close() + if cursor_historical: + cursor_historical.close() + if cnx_historical: + cnx_historical.disconnect() time.sleep(60) continue @@ -380,14 +384,14 @@ def process(logger): " FROM tbl_energy_value " " WHERE utc_date_time >= %s AND utc_date_time <= %s AND is_bad IS NOT TRUE " " ORDER BY point_id, utc_date_time ") - cursor.execute(query, (min_datetime, max_datetime,)) - rows_energy_values = cursor.fetchall() + cursor_historical.execute(query, (min_datetime, max_datetime,)) + rows_energy_values = cursor_historical.fetchall() except Exception as e: logger.error("Error in step 3.1 of clean_energy_value.process " + str(e)) - if cursor: - cursor.close() - if cnx: - cnx.close() + if cursor_historical: + cursor_historical.close() + if cnx_historical: + cnx_historical.disconnect() time.sleep(60) continue @@ -416,6 +420,7 @@ def process(logger): if len(current_point_value_list) > 0: point_value_dict[current_point_id] = current_point_value_list + # reinitialize bad list bad_list = list() for point_id, point_value_list in point_value_dict.items(): @@ -449,14 +454,14 @@ def process(logger): update = (" UPDATE tbl_energy_value " " SET is_bad = TRUE " " WHERE id IN (" + ', '.join(map(str, bad_list)) + ")") - cursor.execute(update, ) - cnx.commit() + cursor_historical.execute(update, ) + cnx_historical.commit() except Exception as e: logger.error("Error in step 3.2 of clean_energy_value.process " + str(e)) - if cursor: - cursor.close() - if cnx: - cnx.close() + if cursor_historical: + cursor_historical.close() + if cnx_historical: + cnx_historical.disconnect() time.sleep(60) continue @@ -544,16 +549,16 @@ def process(logger): " SET is_bad = FALSE " " WHERE utc_date_time >= %s AND utc_date_time < %s AND is_bad IS NULL ") # NOTE: use '<' instead of '<=' in WHERE statement because there may be some new inserted values - cursor.execute(update, (min_datetime, max_datetime,)) - cnx.commit() + cursor_historical.execute(update, (min_datetime, max_datetime,)) + cnx_historical.commit() except Exception as e: logger.error("Error in step 4 of clean_energy_value.process " + str(e)) time.sleep(60) continue finally: - if cursor: - cursor.close() - if cnx: - cnx.close() + if cursor_historical: + cursor_historical.close() + if cnx_historical: + cnx_historical.disconnect() time.sleep(900) diff --git a/myems-cleaning/config.py b/myems-cleaning/config.py index 8cbaf1d9..b2eb0384 100644 --- a/myems-cleaning/config.py +++ b/myems-cleaning/config.py @@ -1,4 +1,3 @@ -# definition of the database myems_system_db = { 'user': 'root', 'password': '!MyEMS1', @@ -18,7 +17,8 @@ myems_historical_db = { # indicates how long analog values and digital values will be kept in database # the longer days the more memory and disc space needed. live_in_days = 365 -# note: By default, energy values in historical db will never be deleted automatically. + +# NOTE: By default, energy values in historical db will never be deleted automatically. # indicates if the program is in debug mode is_debug = False diff --git a/web/src/components/MyEMS/CombinedEquipment/CombinedEquipmentLoad.js b/web/src/components/MyEMS/CombinedEquipment/CombinedEquipmentLoad.js index 36f985c6..725509ca 100644 --- a/web/src/components/MyEMS/CombinedEquipment/CombinedEquipmentLoad.js +++ b/web/src/components/MyEMS/CombinedEquipment/CombinedEquipmentLoad.js @@ -33,6 +33,7 @@ import { comparisonTypeOptions } from '../common/ComparisonTypeOptions'; const DetailedDataTable = loadable(() => import('../common/DetailedDataTable')); +const AssociatedEquipmentTable = loadable(() => import('../common/AssociatedEquipmentTable')); const CombinedEquipmentLoad = ({ setRedirect, setRedirectUrl, t }) => { let current_moment = moment(); @@ -87,6 +88,10 @@ const CombinedEquipmentLoad = ({ setRedirect, setRedirectUrl, t }) => { const [detailedDataTableData, setDetailedDataTableData] = useState([]); const [detailedDataTableColumns, setDetailedDataTableColumns] = useState([{dataField: 'startdatetime', text: t('Datetime'), sort: true}]); + + const [associatedEquipmentTableData, setAssociatedEquipmentTableData] = useState([]); + const [associatedEquipmentTableColumns, setAssociatedEquipmentTableColumns] = useState([{dataField: 'name', text: t('Associated Equipment'), sort: true }]); + const [excelBytesBase64, setExcelBytesBase64] = useState(undefined); useEffect(() => { @@ -291,6 +296,7 @@ const CombinedEquipmentLoad = ({ setRedirect, setRedirectUrl, t }) => { // Reinitialize tables setDetailedDataTableData([]); + setAssociatedEquipmentTableData([]); let isResponseOK = false; fetch(APIBaseURL + '/reports/combinedequipmentload?' + @@ -418,7 +424,44 @@ const CombinedEquipmentLoad = ({ setRedirect, setRedirectUrl, t }) => { }); }); setDetailedDataTableColumns(detailed_column_list); - + let associated_equipment_value_list = []; + if (json['associated_equipment']['associated_equipment_names_array'].length > 0) { + json['associated_equipment']['associated_equipment_names_array'][0].forEach((currentEquipmentName, equipmentIndex) => { + let associated_equipment_value = {}; + associated_equipment_value['id'] = equipmentIndex; + associated_equipment_value['name'] = currentEquipmentName; + json['associated_equipment']['energy_category_names'].forEach((currentValue, energyCategoryIndex) => { + associated_equipment_value['a' + 2 * energyCategoryIndex] = json['associated_equipment']['sub_averages'][energyCategoryIndex][equipmentIndex].toFixed(2); + associated_equipment_value['a' + 2 * energyCategoryIndex + 1] = json['associated_equipment']['sub_maximums'][energyCategoryIndex][equipmentIndex].toFixed(2); + }); + associated_equipment_value_list.push(associated_equipment_value); + }); + }; + + setAssociatedEquipmentTableData(associated_equipment_value_list); + + let associated_equipment_column_list = []; + associated_equipment_column_list.push({ + dataField: 'name', + text: t('Associated Equipment'), + sort: true + }); + json['associated_equipment']['energy_category_names'].forEach((currentValue, index) => { + let unit = json['associated_equipment']['units'][index]; + associated_equipment_column_list.push({ + dataField: 'a' + 2 * index, + text: currentValue + ' ' + t('Average Load') + ' (' + unit + '/H)', + sort: true + }); + associated_equipment_column_list.push({ + dataField: 'a' + (2 * index + 1), + text: currentValue + ' ' + t('Maximum Load') + ' (' + unit + '/H)', + sort: true + }); + }); + + setAssociatedEquipmentTableColumns(associated_equipment_column_list); + setExcelBytesBase64(json['excel_bytes_base64']); // enable submit button @@ -642,6 +685,9 @@ const CombinedEquipmentLoad = ({ setRedirect, setRedirectUrl, t }) => {
+
+ + ); diff --git a/web/src/components/MyEMS/CombinedEquipment/CombinedEquipmentStatistics.js b/web/src/components/MyEMS/CombinedEquipment/CombinedEquipmentStatistics.js index 739fb8e1..11c8a7b9 100644 --- a/web/src/components/MyEMS/CombinedEquipment/CombinedEquipmentStatistics.js +++ b/web/src/components/MyEMS/CombinedEquipment/CombinedEquipmentStatistics.js @@ -33,6 +33,7 @@ import { comparisonTypeOptions } from '../common/ComparisonTypeOptions'; const DetailedDataTable = loadable(() => import('../common/DetailedDataTable')); +const AssociatedEquipmentTable = loadable(() => import('../common/AssociatedEquipmentTable')); const CombinedEquipmentStatistics = ({ setRedirect, setRedirectUrl, t }) => { let current_moment = moment(); @@ -87,6 +88,10 @@ const CombinedEquipmentStatistics = ({ setRedirect, setRedirectUrl, t }) => { const [detailedDataTableData, setDetailedDataTableData] = useState([]); const [detailedDataTableColumns, setDetailedDataTableColumns] = useState([{dataField: 'startdatetime', text: t('Datetime'), sort: true}]); + + const [associatedEquipmentTableData, setAssociatedEquipmentTableData] = useState([]); + const [associatedEquipmentTableColumns, setAssociatedEquipmentTableColumns] = useState([{dataField: 'name', text: t('Associated Equipment'), sort: true }]); + const [excelBytesBase64, setExcelBytesBase64] = useState(undefined); useEffect(() => { @@ -291,6 +296,7 @@ const CombinedEquipmentStatistics = ({ setRedirect, setRedirectUrl, t }) => { // Reinitialize tables setDetailedDataTableData([]); + setAssociatedEquipmentTableData([]); let isResponseOK = false; fetch(APIBaseURL + '/reports/combinedequipmentstatistics?' + @@ -416,6 +422,37 @@ const CombinedEquipmentStatistics = ({ setRedirect, setRedirectUrl, t }) => { }) }); setDetailedDataTableColumns(detailed_column_list); + let associated_equipment_value_list = []; + if (json['associated_equipment']['associated_equipment_names_array'].length > 0) { + json['associated_equipment']['associated_equipment_names_array'][0].forEach((currentEquipmentName, equipmentIndex) => { + let associated_equipment_value = {}; + associated_equipment_value['id'] = equipmentIndex; + associated_equipment_value['name'] = currentEquipmentName; + json['associated_equipment']['energy_category_names'].forEach((currentValue, energyCategoryIndex) => { + associated_equipment_value['a' + energyCategoryIndex] = json['associated_equipment']['subtotals_array'][energyCategoryIndex][equipmentIndex].toFixed(2); + }); + associated_equipment_value_list.push(associated_equipment_value); + }); + }; + + setAssociatedEquipmentTableData(associated_equipment_value_list); + + let associated_equipment_column_list = []; + associated_equipment_column_list.push({ + dataField: 'name', + text: t('Associated Equipment'), + sort: true + }); + json['associated_equipment']['energy_category_names'].forEach((currentValue, index) => { + let unit = json['associated_equipment']['units'][index]; + associated_equipment_column_list.push({ + dataField: 'a' + index, + text: currentValue + ' (' + unit + ')', + sort: true + }); + }); + + setAssociatedEquipmentTableColumns(associated_equipment_column_list); setExcelBytesBase64(json['excel_bytes_base64']); @@ -657,6 +694,9 @@ const CombinedEquipmentStatistics = ({ setRedirect, setRedirectUrl, t }) => {
+
+ + );