Ai
252 Star 2.3K Fork 768

MyEMS/MyEMS能源管理系统

Create your Gitee Account
Explore and code with more than 13.5 million developers,Free private repositories !:)
Sign up
文件
Clone or Download
meterenergy.py 28.48 KB
Copy Edit Raw Blame History
张能远 authored 2025-10-09 21:01 +08:00 . added comments
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542
"""
Meter Energy Report API
This module provides REST API endpoints for generating meter energy reports.
It analyzes energy consumption data from meters, providing comprehensive insights
into energy usage patterns, consumption trends, and optimization opportunities.
Key Features:
- Meter energy consumption analysis
- Base period vs reporting period comparison
- Energy consumption trends and patterns
- Consumption optimization insights
- Excel export functionality
- Performance metrics calculation
Report Components:
- Meter energy consumption summary
- Base period comparison data
- Energy consumption trends
- Consumption optimization recommendations
- Performance indicators
- Usage pattern analysis
The module uses Falcon framework for REST API and includes:
- Database queries for energy data
- Energy consumption calculations
- Trend analysis algorithms
- Excel export via excelexporters
- Multi-language support
- User authentication and authorization
"""
import re
from datetime import datetime, timedelta, timezone
from decimal import Decimal
import falcon
import mysql.connector
import simplejson as json
import config
import excelexporters.meterenergy
from core import utilities
from core.useractivity import access_control, api_key_control
class Reporting:
def __init__(self):
""""Initializes Reporting"""
pass
@staticmethod
def on_options(req, resp):
_ = req
resp.status = falcon.HTTP_200
####################################################################################################################
# PROCEDURES
# Step 1: valid parameters
# Step 2: query the meter and energy category
# Step 3: query associated points
# Step 4: query base period energy consumption
# Step 5: query reporting period energy consumption
# Step 6: query tariff data
# Step 7: query associated points data
# Step 8: construct the report
####################################################################################################################
@staticmethod
def on_get(req, resp):
if 'API-KEY' not in req.headers or \
not isinstance(req.headers['API-KEY'], str) or \
len(str.strip(req.headers['API-KEY'])) == 0:
access_control(req)
else:
api_key_control(req)
print(req.params)
# this procedure accepts meter id or meter uuid to identify a meter
meter_id = req.params.get('meterid')
meter_uuid = req.params.get('meteruuid')
period_type = req.params.get('periodtype')
base_period_start_datetime_local = req.params.get('baseperiodstartdatetime')
base_period_end_datetime_local = req.params.get('baseperiodenddatetime')
reporting_period_start_datetime_local = req.params.get('reportingperiodstartdatetime')
reporting_period_end_datetime_local = req.params.get('reportingperiodenddatetime')
language = req.params.get('language')
quick_mode = req.params.get('quickmode')
################################################################################################################
# Step 1: valid parameters
################################################################################################################
if meter_id is None and meter_uuid is None:
raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST', description='API.INVALID_METER_ID')
if meter_id is not None:
meter_id = str.strip(meter_id)
if not meter_id.isdigit() or int(meter_id) <= 0:
raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
description='API.INVALID_METER_ID')
if meter_uuid is not None:
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)
match = regex.match(str.strip(meter_uuid))
if not bool(match):
raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
description='API.INVALID_METER_UUID')
if period_type is None:
raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
description='API.INVALID_PERIOD_TYPE')
else:
period_type = str.strip(period_type)
if period_type not in ['hourly', 'daily', 'weekly', 'monthly', 'yearly']:
raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
description='API.INVALID_PERIOD_TYPE')
timezone_offset = int(config.utc_offset[1:3]) * 60 + int(config.utc_offset[4:6])
if config.utc_offset[0] == '-':
timezone_offset = -timezone_offset
base_start_datetime_utc = None
if base_period_start_datetime_local is not None and len(str.strip(base_period_start_datetime_local)) > 0:
base_period_start_datetime_local = str.strip(base_period_start_datetime_local)
try:
base_start_datetime_utc = datetime.strptime(base_period_start_datetime_local, '%Y-%m-%dT%H:%M:%S')
except ValueError:
raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
description="API.INVALID_BASE_PERIOD_START_DATETIME")
base_start_datetime_utc = \
base_start_datetime_utc.replace(tzinfo=timezone.utc) - timedelta(minutes=timezone_offset)
# nomalize the start datetime
if config.minutes_to_count == 30 and base_start_datetime_utc.minute >= 30:
base_start_datetime_utc = base_start_datetime_utc.replace(minute=30, second=0, microsecond=0)
else:
base_start_datetime_utc = base_start_datetime_utc.replace(minute=0, second=0, microsecond=0)
base_end_datetime_utc = None
if base_period_end_datetime_local is not None and len(str.strip(base_period_end_datetime_local)) > 0:
base_period_end_datetime_local = str.strip(base_period_end_datetime_local)
try:
base_end_datetime_utc = datetime.strptime(base_period_end_datetime_local, '%Y-%m-%dT%H:%M:%S')
except ValueError:
raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
description="API.INVALID_BASE_PERIOD_END_DATETIME")
base_end_datetime_utc = \
base_end_datetime_utc.replace(tzinfo=timezone.utc) - timedelta(minutes=timezone_offset)
if base_start_datetime_utc is not None and base_end_datetime_utc is not None and \
base_start_datetime_utc >= base_end_datetime_utc:
raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
description='API.INVALID_BASE_PERIOD_END_DATETIME')
if reporting_period_start_datetime_local is None:
raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
description="API.INVALID_REPORTING_PERIOD_START_DATETIME")
else:
reporting_period_start_datetime_local = str.strip(reporting_period_start_datetime_local)
try:
reporting_start_datetime_utc = datetime.strptime(reporting_period_start_datetime_local,
'%Y-%m-%dT%H:%M:%S')
except ValueError:
raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
description="API.INVALID_REPORTING_PERIOD_START_DATETIME")
reporting_start_datetime_utc = \
reporting_start_datetime_utc.replace(tzinfo=timezone.utc) - timedelta(minutes=timezone_offset)
# nomalize the start datetime
if config.minutes_to_count == 30 and reporting_start_datetime_utc.minute >= 30:
reporting_start_datetime_utc = reporting_start_datetime_utc.replace(minute=30, second=0, microsecond=0)
else:
reporting_start_datetime_utc = reporting_start_datetime_utc.replace(minute=0, second=0, microsecond=0)
if reporting_period_end_datetime_local is None:
raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
description="API.INVALID_REPORTING_PERIOD_END_DATETIME")
else:
reporting_period_end_datetime_local = str.strip(reporting_period_end_datetime_local)
try:
reporting_end_datetime_utc = datetime.strptime(reporting_period_end_datetime_local,
'%Y-%m-%dT%H:%M:%S')
except ValueError:
raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
description="API.INVALID_REPORTING_PERIOD_END_DATETIME")
reporting_end_datetime_utc = reporting_end_datetime_utc.replace(tzinfo=timezone.utc) - \
timedelta(minutes=timezone_offset)
if reporting_start_datetime_utc >= reporting_end_datetime_utc:
raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
description='API.INVALID_REPORTING_PERIOD_END_DATETIME')
# if turn quick mode on, do not return parameters data and excel file
is_quick_mode = False
if quick_mode is not None and \
len(str.strip(quick_mode)) > 0 and \
str.lower(str.strip(quick_mode)) in ('true', 't', 'on', 'yes', 'y'):
is_quick_mode = True
trans = utilities.get_translation(language)
trans.install()
_ = trans.gettext
################################################################################################################
# Step 2: query the meter and energy category
################################################################################################################
cnx_system = mysql.connector.connect(**config.myems_system_db)
cursor_system = cnx_system.cursor()
cnx_energy = mysql.connector.connect(**config.myems_energy_db)
cursor_energy = cnx_energy.cursor()
cnx_historical = mysql.connector.connect(**config.myems_historical_db)
cursor_historical = cnx_historical.cursor()
if meter_id is not None:
cursor_system.execute(" SELECT m.id, m.name, m.cost_center_id, m.energy_category_id, "
" ec.name, ec.unit_of_measure, ec.kgce, ec.kgco2e "
" FROM tbl_meters m, tbl_energy_categories ec "
" WHERE m.id = %s AND m.energy_category_id = ec.id ", (meter_id,))
row_meter = cursor_system.fetchone()
elif meter_uuid is not None:
cursor_system.execute(" SELECT m.id, m.name, m.cost_center_id, m.energy_category_id, "
" ec.name, ec.unit_of_measure, ec.kgce, ec.kgco2e "
" FROM tbl_meters m, tbl_energy_categories ec "
" WHERE m.uuid = %s AND m.energy_category_id = ec.id ", (meter_uuid,))
row_meter = cursor_system.fetchone()
if row_meter is None:
if cursor_system:
cursor_system.close()
if cnx_system:
cnx_system.close()
if cursor_energy:
cursor_energy.close()
if cnx_energy:
cnx_energy.close()
if cursor_historical:
cursor_historical.close()
if cnx_historical:
cnx_historical.close()
raise falcon.HTTPError(status=falcon.HTTP_404, title='API.NOT_FOUND', description='API.METER_NOT_FOUND')
meter = dict()
meter['id'] = row_meter[0]
meter['name'] = row_meter[1]
meter['cost_center_id'] = row_meter[2]
meter['energy_category_id'] = row_meter[3]
meter['energy_category_name'] = row_meter[4]
meter['unit_of_measure'] = row_meter[5]
meter['kgce'] = row_meter[6]
meter['kgco2e'] = row_meter[7]
################################################################################################################
# Step 3: query associated points
################################################################################################################
point_list = list()
cursor_system.execute(" SELECT p.id, p.name, p.units, p.object_type "
" FROM tbl_meters m, tbl_meters_points mp, tbl_points p "
" WHERE m.id = %s AND m.id = mp.meter_id AND mp.point_id = p.id "
" ORDER BY p.id ", (meter['id'],))
rows_points = cursor_system.fetchall()
if rows_points is not None and len(rows_points) > 0:
for row in rows_points:
point_list.append({"id": row[0], "name": row[1], "units": row[2], "object_type": row[3]})
################################################################################################################
# Step 4: query base period energy consumption
################################################################################################################
cnx_energy = mysql.connector.connect(**config.myems_energy_db)
cursor_energy = cnx_energy.cursor()
query = (" SELECT start_datetime_utc, actual_value "
" FROM tbl_meter_hourly "
" WHERE meter_id = %s "
" AND start_datetime_utc >= %s "
" AND start_datetime_utc < %s "
" ORDER BY start_datetime_utc ")
cursor_energy.execute(query, (meter['id'], base_start_datetime_utc, base_end_datetime_utc))
rows_meter_hourly = cursor_energy.fetchall()
rows_meter_periodically = utilities.aggregate_hourly_data_by_period(rows_meter_hourly,
base_start_datetime_utc,
base_end_datetime_utc,
period_type)
base = dict()
base['timestamps'] = list()
base['values'] = list()
base['total_in_category'] = Decimal(0.0)
base['total_in_kgce'] = Decimal(0.0)
base['total_in_kgco2e'] = Decimal(0.0)
for row_meter_periodically in rows_meter_periodically:
current_datetime_local = row_meter_periodically[0].replace(tzinfo=timezone.utc) + \
timedelta(minutes=timezone_offset)
if period_type == 'hourly':
current_datetime = current_datetime_local.isoformat()[0:19]
elif period_type == 'daily':
current_datetime = current_datetime_local.isoformat()[0:10]
elif period_type == 'weekly':
current_datetime = current_datetime_local.isoformat()[0:10]
elif period_type == 'monthly':
current_datetime = current_datetime_local.isoformat()[0:7]
elif period_type == 'yearly':
current_datetime = current_datetime_local.isoformat()[0:4]
actual_value = Decimal(0.0) if row_meter_periodically[1] is None else row_meter_periodically[1]
base['timestamps'].append(current_datetime)
base['values'].append(actual_value)
base['total_in_category'] += actual_value
base['total_in_kgce'] += actual_value * meter['kgce']
base['total_in_kgco2e'] += actual_value * meter['kgco2e']
################################################################################################################
# Step 5: query reporting period energy consumption
################################################################################################################
query = (" SELECT start_datetime_utc, actual_value "
" FROM tbl_meter_hourly "
" WHERE meter_id = %s "
" AND start_datetime_utc >= %s "
" AND start_datetime_utc < %s "
" ORDER BY start_datetime_utc ")
cursor_energy.execute(query, (meter['id'], reporting_start_datetime_utc, reporting_end_datetime_utc))
rows_meter_hourly = cursor_energy.fetchall()
rows_meter_periodically = utilities.aggregate_hourly_data_by_period(rows_meter_hourly,
reporting_start_datetime_utc,
reporting_end_datetime_utc,
period_type)
reporting = dict()
reporting['timestamps'] = list()
reporting['values'] = list()
reporting['rates'] = list()
reporting['total_in_category'] = Decimal(0.0)
reporting['total_in_kgce'] = Decimal(0.0)
reporting['total_in_kgco2e'] = Decimal(0.0)
reporting['toppeak'] = Decimal(0.0)
reporting['onpeak'] = Decimal(0.0)
reporting['midpeak'] = Decimal(0.0)
reporting['offpeak'] = Decimal(0.0)
reporting['deep'] = Decimal(0.0)
for row_meter_periodically in rows_meter_periodically:
current_datetime_local = row_meter_periodically[0].replace(tzinfo=timezone.utc) + \
timedelta(minutes=timezone_offset)
if period_type == 'hourly':
current_datetime = current_datetime_local.isoformat()[0:19]
elif period_type == 'daily':
current_datetime = current_datetime_local.isoformat()[0:10]
elif period_type == 'weekly':
current_datetime = current_datetime_local.isoformat()[0:10]
elif period_type == 'monthly':
current_datetime = current_datetime_local.isoformat()[0:7]
elif period_type == 'yearly':
current_datetime = current_datetime_local.isoformat()[0:4]
actual_value = Decimal(0.0) if row_meter_periodically[1] is None else row_meter_periodically[1]
reporting['timestamps'].append(current_datetime)
reporting['values'].append(actual_value)
reporting['total_in_category'] += actual_value
reporting['total_in_kgce'] += actual_value * meter['kgce']
reporting['total_in_kgco2e'] += actual_value * meter['kgco2e']
for index, value in enumerate(reporting['values']):
if index < len(base['values']) and base['values'][index] != 0 and value != 0:
reporting['rates'].append((value - base['values'][index]) / base['values'][index])
else:
reporting['rates'].append(None)
energy_category_tariff_dict = utilities.get_energy_category_peak_types(meter['cost_center_id'],
meter['energy_category_id'],
reporting_start_datetime_utc,
reporting_end_datetime_utc)
for row_meter_hourly in rows_meter_hourly:
peak_type = energy_category_tariff_dict.get(row_meter_hourly[0], None)
if peak_type == 'toppeak':
reporting['toppeak'] += row_meter_hourly[1]
elif peak_type == 'onpeak':
reporting['onpeak'] += row_meter_hourly[1]
elif peak_type == 'midpeak':
reporting['midpeak'] += row_meter_hourly[1]
elif peak_type == 'offpeak':
reporting['offpeak'] += row_meter_hourly[1]
elif peak_type == 'deep':
reporting['deep'] += row_meter_hourly[1]
################################################################################################################
# Step 6: query tariff data
################################################################################################################
parameters_data = dict()
parameters_data['names'] = list()
parameters_data['timestamps'] = list()
parameters_data['values'] = list()
if config.is_tariff_appended and not is_quick_mode:
tariff_dict = utilities.get_energy_category_tariffs(meter['cost_center_id'],
meter['energy_category_id'],
reporting_start_datetime_utc,
reporting_end_datetime_utc)
tariff_timestamp_list = list()
tariff_value_list = list()
for k, v in tariff_dict.items():
# convert k from utc to local
k = k + timedelta(minutes=timezone_offset)
tariff_timestamp_list.append(k.isoformat()[0:19])
tariff_value_list.append(v)
parameters_data['names'].append(_('Tariff') + '-' + meter['energy_category_name'])
parameters_data['timestamps'].append(tariff_timestamp_list)
parameters_data['values'].append(tariff_value_list)
################################################################################################################
# Step 7: query associated points data
################################################################################################################
if not is_quick_mode:
for point in point_list:
point_values = []
point_timestamps = []
if point['object_type'] == 'ENERGY_VALUE':
query = (" SELECT utc_date_time, actual_value "
" FROM tbl_energy_value "
" WHERE point_id = %s "
" AND utc_date_time BETWEEN %s AND %s "
" ORDER BY utc_date_time ")
cursor_historical.execute(query, (point['id'],
reporting_start_datetime_utc,
reporting_end_datetime_utc))
rows = cursor_historical.fetchall()
if rows is not None and len(rows) > 0:
for row in rows:
current_datetime_local = row[0].replace(tzinfo=timezone.utc) + \
timedelta(minutes=timezone_offset)
current_datetime = current_datetime_local.isoformat()[0:19]
point_timestamps.append(current_datetime)
point_values.append(row[1])
elif point['object_type'] == 'ANALOG_VALUE':
query = (" SELECT utc_date_time, actual_value "
" FROM tbl_analog_value "
" WHERE point_id = %s "
" AND utc_date_time BETWEEN %s AND %s "
" ORDER BY utc_date_time ")
cursor_historical.execute(query, (point['id'],
reporting_start_datetime_utc,
reporting_end_datetime_utc))
rows = cursor_historical.fetchall()
if rows is not None and len(rows) > 0:
for row in rows:
current_datetime_local = row[0].replace(tzinfo=timezone.utc) + \
timedelta(minutes=timezone_offset)
current_datetime = current_datetime_local.isoformat()[0:19]
point_timestamps.append(current_datetime)
point_values.append(row[1])
elif point['object_type'] == 'DIGITAL_VALUE':
query = (" SELECT utc_date_time, actual_value "
" FROM tbl_digital_value "
" WHERE point_id = %s "
" AND utc_date_time BETWEEN %s AND %s "
" ORDER BY utc_date_time ")
cursor_historical.execute(query, (point['id'],
reporting_start_datetime_utc,
reporting_end_datetime_utc))
rows = cursor_historical.fetchall()
if rows is not None and len(rows) > 0:
for row in rows:
current_datetime_local = row[0].replace(tzinfo=timezone.utc) + \
timedelta(minutes=timezone_offset)
current_datetime = current_datetime_local.isoformat()[0:19]
point_timestamps.append(current_datetime)
point_values.append(row[1])
parameters_data['names'].append(point['name'] + ' (' + point['units'] + ')')
parameters_data['timestamps'].append(point_timestamps)
parameters_data['values'].append(point_values)
################################################################################################################
# Step 8: construct the report
################################################################################################################
if cursor_system:
cursor_system.close()
if cnx_system:
cnx_system.close()
if cursor_energy:
cursor_energy.close()
if cnx_energy:
cnx_energy.close()
if cursor_historical:
cursor_historical.close()
if cnx_historical:
cnx_historical.close()
result = {
"meter": {
"cost_center_id": meter['cost_center_id'],
"energy_category_id": meter['energy_category_id'],
"energy_category_name": meter['energy_category_name'],
"unit_of_measure": meter['unit_of_measure'],
"kgce": meter['kgce'],
"kgco2e": meter['kgco2e'],
"name": meter['name'],
"id": meter['id'],
},
"base_period": {
"total_in_category": base['total_in_category'],
"total_in_kgce": base['total_in_kgce'],
"total_in_kgco2e": base['total_in_kgco2e'],
"timestamps": base['timestamps'],
"values": base['values']
},
"reporting_period": {
"increment_rate":
(reporting['total_in_category'] - base['total_in_category']) / base['total_in_category']
if base['total_in_category'] != Decimal(0.0) else None,
"total_in_category": reporting['total_in_category'],
"total_in_kgce": reporting['total_in_kgce'],
"total_in_kgco2e": reporting['total_in_kgco2e'],
"toppeak": reporting['toppeak'],
"onpeak": reporting['onpeak'],
"midpeak": reporting['midpeak'],
"offpeak": reporting['offpeak'],
"deep": reporting['deep'],
"timestamps": reporting['timestamps'],
"values": reporting['values'],
"rates": reporting['rates'],
},
"parameters": {
"names": parameters_data['names'],
"timestamps": parameters_data['timestamps'],
"values": parameters_data['values']
},
}
# export result to Excel file and then encode the file to base64 string
if not is_quick_mode:
result['excel_bytes_base64'] = \
excelexporters.meterenergy.export(result,
meter['name'],
reporting_period_start_datetime_local,
reporting_period_end_datetime_local,
base_period_start_datetime_local,
base_period_end_datetime_local,
period_type,
language)
resp.text = json.dumps(result)
Loading...
马建仓 AI 助手
尝试更多
代码解读
代码找茬
代码优化
Python
1
https://gitee.com/myems/myems.git
git@gitee.com:myems/myems.git
myems
myems
MyEMS能源管理系统
master

Search