add rounding to nearest 5m n dash

This commit is contained in:
Radek
2025-05-22 11:20:46 +01:00
parent beacfd79ed
commit 4551480e41
2 changed files with 188 additions and 5 deletions

View File

@@ -0,0 +1,163 @@
import dash
from dash import dcc, html, Input, Output
import plotly.express as px
import pandas as pd
import sqlite3
from datetime import datetime, timedelta
# Initialize the Dash app
app = dash.Dash(__name__)
# Connect to the SQLite database
def get_db_connection():
conn = sqlite3.connect('power_data.db')
return conn
# Function to fetch data from the database
def fetch_data(time_range):
conn = get_db_connection()
query = f"""
SELECT * FROM building_totals
WHERE timestamp >= datetime('now', '-{time_range} hours')
"""
df = pd.read_sql_query(query, conn)
conn.close()
return df
# Function to calculate kWh and round timestamps for building totals
def calculate_building_kwh(df):
df['timestamp'] = pd.to_datetime(df['timestamp'])
df['timestamp'] = df['timestamp'].dt.round('5min') # Round to 5-minute intervals
df = df.set_index('timestamp')
df['kWh'] = df['total_power'] * (5 / 60) # Convert power to kWh for 5-minute intervals
return df
# Function to calculate kWh and round timestamps for room and customer breakdowns
def calculate_breakdown_kwh(df):
df['timestamp'] = pd.to_datetime(df['timestamp'])
df['timestamp'] = df['timestamp'].dt.round('5min') # Round to 5-minute intervals
df = df.set_index('timestamp')
df['kWh'] = df['power'] * (5 / 60) # Convert power to kWh for 5-minute intervals
return df
# Define the layout of the dashboard
app.layout = html.Div([
html.H1("Power Usage Overview", style={'textAlign': 'center'}),
dcc.Dropdown(
id='time-range',
options=[
{'label': '6 Hours', 'value': '6'},
{'label': '12 Hours', 'value': '12'},
{'label': '24 Hours', 'value': '24'},
{'label': '1 Week', 'value': '168'},
{'label': '2 Weeks', 'value': '336'},
{'label': '1 Month', 'value': '720'},
{'label': '2 Months', 'value': '1440'},
{'label': '1 Year', 'value': '8760'},
],
value='6',
style={'width': '200px', 'margin': '0 auto'}
),
dcc.Graph(id='building-graph', style={'width': '100%', 'height': '400px'}),
dcc.Tabs(id='tabs', value='room-breakdown', children=[
dcc.Tab(label='Room Breakdown', value='room-breakdown'),
dcc.Tab(label='Customer Breakdown', value='customer-breakdown'),
]),
html.Div([
dcc.Dropdown(id='drill-down', multi=False, style={'width': '200px', 'margin': '0 auto'}),
dcc.Graph(id='breakdown-graph', style={'width': '100%', 'height': '400px'}),
])
])
# Callback to update the building total graph
@app.callback(
Output('building-graph', 'figure'),
[Input('time-range', 'value')]
)
def update_building_graph(time_range):
df = fetch_data(time_range)
df = calculate_building_kwh(df)
fig = px.line(df, x=df.index, y=['total_current', 'total_power', 'kWh'],
labels={'value': 'Value', 'variable': 'Metric'},
title='Building Total Metrics')
fig.update_layout(legend_title_text='Metrics')
# Update legend to show recent values
for trace in fig.data:
if trace.name == 'kWh':
trace.name = f"{trace.name}: {df['kWh'].sum():.2f}"
else:
trace.name = f"{trace.name}: {df[trace.name].iloc[-1]:.2f}"
return fig
# Callback to update the drill-down dropdown
@app.callback(
Output('drill-down', 'options'),
[Input('tabs', 'value')]
)
def update_drill_down_options(tab):
conn = get_db_connection()
if tab == 'room-breakdown':
query = "SELECT DISTINCT room_number FROM room_breakdown"
else:
query = "SELECT DISTINCT customer_name FROM customer_breakdown"
df = pd.read_sql_query(query, conn)
conn.close()
return [{'label': i, 'value': i} for i in df.iloc[:, 0]]
# Callback to set the default value for the drill-down dropdown
@app.callback(
Output('drill-down', 'value'),
[Input('drill-down', 'options')]
)
def set_drill_down_value(options):
if options:
return options[0]['value']
return None
# Callback to update the breakdown graph
@app.callback(
Output('breakdown-graph', 'figure'),
[Input('tabs', 'value'),
Input('drill-down', 'value'),
Input('time-range', 'value')]
)
def update_breakdown_graph(tab, drill_down, time_range):
conn = get_db_connection()
if tab == 'room-breakdown':
query = f"""
SELECT * FROM room_breakdown
WHERE room_number = '{drill_down}'
AND timestamp >= datetime('now', '-{time_range} hours')
"""
else:
query = f"""
SELECT * FROM customer_breakdown
WHERE customer_name = '{drill_down}'
AND timestamp >= datetime('now', '-{time_range} hours')
"""
df = pd.read_sql_query(query, conn)
conn.close()
df = calculate_breakdown_kwh(df)
fig = px.line(df, x=df.index, y=['current', 'power', 'kWh'],
labels={'value': 'Value', 'variable': 'Metric'},
title=f'{tab.replace("-", " ").title()} Metrics')
fig.update_layout(legend_title_text='Metrics')
# Update legend to show recent values
for trace in fig.data:
if trace.name == 'kWh':
trace.name = f"{trace.name}: {df['kWh'].sum():.2f}"
else:
trace.name = f"{trace.name}: {df[trace.name].iloc[-1]:.2f}"
return fig
# Run the app
if __name__ == '__main__':
app.run(host='0.0.0.0', port=8050, debug=True)

View File

@@ -3,7 +3,7 @@ import requests
from collections import defaultdict from collections import defaultdict
import argparse import argparse
import sqlite3 import sqlite3
from datetime import datetime from datetime import datetime, timedelta
import time import time
# Configuration # Configuration
@@ -55,14 +55,31 @@ def create_tables(conn):
except sqlite3.Error as e: except sqlite3.Error as e:
print(e) print(e)
def round_to_nearest_5_minutes(dt):
"""Round a datetime object to the nearest 5-minute interval."""
# Calculate the number of seconds since the last 5-minute interval
seconds_since_last_interval = dt.minute % 5 * 60 + dt.second + dt.microsecond / 1e6
# Round to the nearest 5-minute interval
if seconds_since_last_interval < 150: # 150 seconds = 2.5 minutes
rounded_dt = dt - timedelta(seconds=seconds_since_last_interval)
else:
rounded_dt = dt + timedelta(seconds=300 - seconds_since_last_interval) # 300 seconds = 5 minutes
# Set seconds and microseconds to zero
rounded_dt = rounded_dt.replace(second=0, microsecond=0)
return rounded_dt
def insert_building_total(conn, total_current, total_power): def insert_building_total(conn, total_current, total_power):
"""Insert building total data into the database.""" """Insert building total data into the database."""
try: try:
cursor = conn.cursor() cursor = conn.cursor()
rounded_timestamp = round_to_nearest_5_minutes(datetime.now())
cursor.execute(''' cursor.execute('''
INSERT INTO building_totals (total_current, total_power, timestamp) INSERT INTO building_totals (total_current, total_power, timestamp)
VALUES (?, ?, ?) VALUES (?, ?, ?)
''', (round(total_current, 3), round(total_power, 3), datetime.now().isoformat())) ''', (round(total_current, 3), round(total_power, 3), rounded_timestamp.isoformat()))
conn.commit() conn.commit()
except sqlite3.Error as e: except sqlite3.Error as e:
print(e) print(e)
@@ -71,10 +88,11 @@ def insert_room_breakdown(conn, room_number, current, power):
"""Insert room breakdown data into the database.""" """Insert room breakdown data into the database."""
try: try:
cursor = conn.cursor() cursor = conn.cursor()
rounded_timestamp = round_to_nearest_5_minutes(datetime.now())
cursor.execute(''' cursor.execute('''
INSERT INTO room_breakdown (room_number, current, power, timestamp) INSERT INTO room_breakdown (room_number, current, power, timestamp)
VALUES (?, ?, ?, ?) VALUES (?, ?, ?, ?)
''', (room_number, round(current, 3), round(power, 3), datetime.now().isoformat())) ''', (room_number, round(current, 3), round(power, 3), rounded_timestamp.isoformat()))
conn.commit() conn.commit()
except sqlite3.Error as e: except sqlite3.Error as e:
print(e) print(e)
@@ -83,14 +101,16 @@ def insert_customer_breakdown(conn, customer_name, current, power):
"""Insert customer breakdown data into the database.""" """Insert customer breakdown data into the database."""
try: try:
cursor = conn.cursor() cursor = conn.cursor()
rounded_timestamp = round_to_nearest_5_minutes(datetime.now())
cursor.execute(''' cursor.execute('''
INSERT INTO customer_breakdown (customer_name, current, power, timestamp) INSERT INTO customer_breakdown (customer_name, current, power, timestamp)
VALUES (?, ?, ?, ?) VALUES (?, ?, ?, ?)
''', (customer_name, round(current, 3), round(power, 3), datetime.now().isoformat())) ''', (customer_name, round(current, 3), round(power, 3), rounded_timestamp.isoformat()))
conn.commit() conn.commit()
except sqlite3.Error as e: except sqlite3.Error as e:
print(e) print(e)
def get_device_ids(debug=False): def get_device_ids(debug=False):
"""Fetch all device IDs from the LibreNMS API.""" """Fetch all device IDs from the LibreNMS API."""
response = requests.get(f'http://{LIBRENMS_IP}/api/v0/devices', headers=HEADERS, verify=False) response = requests.get(f'http://{LIBRENMS_IP}/api/v0/devices', headers=HEADERS, verify=False)
@@ -286,5 +306,5 @@ if __name__ == '__main__':
main(debug=args.debug, update_db=not args.no_db_update, concurrency_level=args.concurrency_level) main(debug=args.debug, update_db=not args.no_db_update, concurrency_level=args.concurrency_level)
runs += 1 runs += 1
if args.runs is None: if args.runs is None:
time.sleep(240) # Wait for 5 minutes before the next run time.sleep(250) # Wait for 5 minutes before the next run