BajiraoS1's picture
Update app.py
b1f6d9e verified
import os
import pandas as pd
from datetime import datetime
#from openpyxl import load_workbook
from IPython.display import FileLink, display
from Eversense4_API_2 import Eversense4_API
import numpy as np
import random
from sklearn.preprocessing import MinMaxScaler
import pickle
import gradio as gr
import plotly.graph_objects as go
# Load credentials from environment variables
USERNAME = os.getenv("EVERSENSE_USERNAME", "pgautam@forbesmarshall.com")
PASSWORD = os.getenv("EVERSENSE_PASSWORD", "pranjal123@")
# Initialize API object
apiObj = Eversense4_API(host="https://eversense.forbesmarshall.com")
# Log in securely
apiObj.login(USERNAME, PASSWORD)
# Define start and end timestamps
start = datetime(2024, 12, 1) # Fixed start date
end = datetime.now() # Current time
startTS = int(start.timestamp() * 1000) # Convert to milliseconds
endTS = int(end.timestamp() * 1000)
#print("Start Timestamp:", startTS)
#print("End Timestamp:", endTS)
# Fetch telemetry data
keys = [
'CLD_TTL_JUICE_FLOW', 'CLD_SK1_FM1_FLOW', 'CLD_SK2_FM1_FLOW',
'CLD_SK3_FM1_FLOW', 'CLD_SK4_FM1_FLOW', 'CLD_SK5_FM1_FLOW', 'CLD_FT_TPS'
]
telemetry_response = apiObj.getDeviceDataValues(
'51f7d6d0-9dd0-11ef-9e72-3915b0e66e63',
keys=keys,
startTS=startTS,
endTS=endTS,
interval=60000,
limit=500000
)
# Process data
df = apiObj.processData(telemetry_response)
# Convert all columns except 'timestamp' to float
if 'timestamp' in df.columns:
df.loc[:, df.columns != 'timestamp'] = df.loc[:, df.columns != 'timestamp'].apply(pd.to_numeric, errors='coerce')
else:
df = df.apply(pd.to_numeric, errors='coerce')
df = df.drop(columns=['Timestamp','ts'])
df.reset_index(inplace=True)
# Define the column mapping
column_mapping = {
'CLD_TTL_JUICE_FLOW': 'total_juice_flow',
'CLD_SK1_FM1_FLOW': 'SK1_juice_flow',
'CLD_SK2_FM1_FLOW': 'SK2_juice_flow',
'CLD_SK3_FM1_FLOW': 'SK3_juice_flow',
'CLD_SK4_FM1_FLOW': 'SK4_juice_flow',
'CLD_SK5_FM1_FLOW': 'SK5_juice_flow',
'CLD_FT_TPS': 'total_steam_flow',
'Timestamp': 'Timestamp'
}
# Rename the columns in the DataFrame
df.rename(columns=column_mapping, inplace=True)
#df
df['Steam_Economy'] = df.apply(
lambda row: row['total_juice_flow'] / row['total_steam_flow'] if row['total_steam_flow'] != 0 else None,
axis=1
)
columns_to_check = ['SK1_juice_flow', 'SK2_juice_flow', 'SK3_juice_flow',
'SK4_juice_flow', 'SK5_juice_flow', 'total_juice_flow',
'total_steam_flow', 'Steam_Economy']
df = df[(df[columns_to_check] >= 0).all(axis=1)]
import numpy as np
# Define columns for outlier detection
columns_to_check = ['SK1_juice_flow', 'SK2_juice_flow', 'SK3_juice_flow',
'SK4_juice_flow', 'SK5_juice_flow', 'total_juice_flow',
'total_steam_flow', 'Steam_Economy']
# Function to remove outliers using IQR
def remove_outliers_iqr(df, columns):
for col in columns:
Q1 = df[col].quantile(0.25) # First quartile (25th percentile)
Q3 = df[col].quantile(0.75) # Third quartile (75th percentile)
IQR = Q3 - Q1 # Interquartile range
lower_bound = Q1 - 1.5 * IQR
upper_bound = Q3 + 1.5 * IQR
# Remove rows where the column value is outside the IQR range
df = df[(df[col] >= lower_bound) & (df[col] <= upper_bound)]
return df
# Remove negative values first
df = df[(df[columns_to_check] >= 0).all(axis=1)]
# Apply IQR outlier removal
df = remove_outliers_iqr(df, columns_to_check)
#allocation of steam
# Define operating steam ranges
steam_limits = {
"SK1_steam_flow": (110, 140),
"SK2_steam_flow": (90, 110),
"SK3_steam_flow": (90, 110),
"SK4_steam_flow": (80, 100),
"SK5_steam_flow": (110, 140),
}
# Function to distribute steam dynamically
def distribute_steam(df):
df = df.copy() # Work on a copy to avoid modifying original dataframe
# Create new columns for steam flows
for sk in ["SK1_steam_flow", "SK2_steam_flow", "SK3_steam_flow", "SK4_steam_flow", "SK5_steam_flow"]:
df[sk] = 0.0
for i, row in df.iterrows():
total_steam = row["total_steam_flow"]
# Get juice flow values
juice_flows = {
"SK1_juice_flow": row["SK1_juice_flow"],
"SK2_juice_flow": row["SK2_juice_flow"],
"SK3_juice_flow": row["SK3_juice_flow"],
"SK4_juice_flow": row["SK4_juice_flow"],
"SK5_juice_flow": row["SK5_juice_flow"],
}
# Identify operational SKs (juice flow > 20)
operational_sks = [sk for sk, val in juice_flows.items() if val > 20]
# Ensure exactly 3 SKs are operational
if len(operational_sks) > 3:
operational_sks = operational_sks[:3]
elif len(operational_sks) < 3:
continue # Skip row if less than 3 SKs are operational
# Decide which of SK1 or SK5 should run
if "SK1_juice_flow" in operational_sks and "SK5_juice_flow" in operational_sks:
if row["SK1_juice_flow"] > row["SK5_juice_flow"]:
operational_sks.remove("SK5_juice_flow")
else:
operational_sks.remove("SK1_juice_flow")
# Allocate steam based on distribution percentages
steam_distribution = {}
if "SK1_juice_flow" in operational_sks:
steam_distribution["SK1_steam_flow"] = 0.5 * total_steam
elif "SK5_juice_flow" in operational_sks:
steam_distribution["SK5_steam_flow"] = 0.5 * total_steam
if "SK4_juice_flow" in operational_sks:
steam_distribution["SK4_steam_flow"] = 0.2 * total_steam
remaining_steam = total_steam - sum(steam_distribution.values())
sk2_sk3_count = sum(1 for sk in ["SK2_juice_flow", "SK3_juice_flow"] if sk in operational_sks)
if sk2_sk3_count > 0:
per_sk_steam = remaining_steam / sk2_sk3_count
if "SK2_juice_flow" in operational_sks:
steam_distribution["SK2_steam_flow"] = per_sk_steam
if "SK3_juice_flow" in operational_sks:
steam_distribution["SK3_steam_flow"] = per_sk_steam
# Adjust for steam limits dynamically
total_allocated = sum(steam_distribution.values())
if total_allocated > total_steam:
factor = total_steam / total_allocated
steam_distribution = {k: v * factor for k, v in steam_distribution.items()}
# Ensure individual steam values lie within their respective limits
for sk, steam_value in steam_distribution.items():
min_val, max_val = steam_limits[sk]
if steam_value < min_val:
steam_distribution[sk] = min_val
elif steam_value > max_val:
steam_distribution[sk] = max_val
# Normalize again to ensure sum equals total_steam_flow
total_allocated = sum(steam_distribution.values())
if total_allocated > 0:
factor = total_steam / total_allocated
steam_distribution = {k: v * factor for k, v in steam_distribution.items()}
# Assign values to DataFrame
for sk, val in steam_distribution.items():
df.at[i, sk] = float(val)
return df
# Apply the function
df = distribute_steam(df)
# Set random seed for reproducibility
random.seed(42) # Choose a seed value
np.random.seed(42) # Ensure numpy randomness is also controlled
class QLearningOptimizer:
def __init__(self, alpha=0.1, gamma=0.9, epsilon=0.2):
self.alpha = alpha # Learning rate
self.gamma = gamma # Discount factor
self.epsilon = epsilon # Exploration rate
self.q_table = {} # Q-table as a dictionary
def get_q_values(self, state):
"""Retrieve Q-values for a given state, initialize if unseen"""
return self.q_table.setdefault(state, np.zeros(3)) # 3 actions (decrease, maintain, increase)
def choose_action(self, state):
"""Epsilon-greedy action selection"""
if random.uniform(0, 1) < self.epsilon:
return random.choice([-1]) # Only explore: decrease steam flow
else:
return np.argmax(self.get_q_values(state)) - 1 # Exploit: best learned action (decrease)
def update_q_table(self, state, action, reward, next_state):
"""Update Q-values using Bellman equation"""
q_values = self.get_q_values(state)
next_q_values = self.get_q_values(next_state)
best_next_q = np.max(next_q_values)
q_values[action + 1] = q_values[action + 1] + self.alpha * (reward + self.gamma * best_next_q - q_values[action + 1])
self.q_table[state] = q_values # Store updated Q-values
def save_model(self, filename="q_table.pkl"):
with open(filename, "wb") as f:
pickle.dump(self.q_table, f)
def load_model(self, filename="q_table.pkl"):
with open(filename, "rb") as f:
self.q_table = pickle.load(f)
def calculate_reward(steam_economy):
"""Reward function based on Steam Economy closeness to 2.75"""
return -abs(steam_economy - 2.75) # Negative deviation penalty
def optimize_steam_flow(df):
"""Optimize steam flow using Q-learning"""
ql = QLearningOptimizer()
latest_data = df.iloc[-1] # Get the latest timestamp data
# Identify operational SKs (Steam flow > 0)
operational_sks = [sk for sk in ['SK1_steam_flow', 'SK2_steam_flow', 'SK3_steam_flow', 'SK4_steam_flow', 'SK5_steam_flow']
if latest_data[sk] > 0]
if len(operational_sks) != 3:
print("Warning: Data should have exactly 3 operational SKs.")
return None
total_steam_flow = latest_data['total_steam_flow']
steam_economy = latest_data['Steam_Economy']
# Normalize the state
state = tuple([latest_data[sk] for sk in operational_sks] + [total_steam_flow])
# Choose actions for each operational SK (Only Decrease Allowed)
recommendations = {}
for sk in operational_sks:
action = -1 # Force reduction
reduction = random.randint(10, 15) # Ensure reduction is between 10 and 15
recommended_value = max(0, latest_data[sk] - reduction) # Prevent negative values
recommendations[sk] = recommended_value
# Ensure total recommended steam flow is reduced
total_reduction = random.randint(10, 15)
recommended_total_steam_flow = max(0, total_steam_flow - total_reduction)
# Adjust individual SK flows proportionally if needed
total_recommended_sk_flow = sum(recommendations.values())
difference = recommended_total_steam_flow - total_recommended_sk_flow
if abs(difference) > 0: # If there's a mismatch, adjust proportionally
sk_adjustments = {sk: recommendations[sk] + (difference * (recommendations[sk] / total_recommended_sk_flow))
for sk in operational_sks}
recommendations = {sk: max(0, round(val)) for sk, val in sk_adjustments.items()} # Ensure no negatives
# Ensure total recommended steam flow is strictly less than current
assert sum(recommendations.values()) < total_steam_flow, "Error: Steam consumption should be reduced!"
# New state after taking actions
new_state = tuple(list(recommendations.values()) + [recommended_total_steam_flow])
reward = calculate_reward(steam_economy)
# Update Q-table for each SK independently
for sk in operational_sks:
ql.update_q_table(state, action, reward, new_state)
# Save the updated model
ql.save_model()
# Return current and recommended steam flow values
return {
"current": {sk: latest_data[sk] for sk in operational_sks},
"recommended": recommendations,
"current_total_steam_flow": total_steam_flow,
"recommended_total_steam_flow": sum(recommendations.values()) # Ensure it matches
}
# Call function with your dataframe 'df'
result = optimize_steam_flow(df)
# Display result
# if result:
# print("Current Steam Flow:", result["current"])
# print("Recommended Steam Flow:", result["recommended"])
# print("Current Total Steam Flow:", result["current_total_steam_flow"])
# print("Recommended Total Steam Flow:", result["recommended_total_steam_flow"])
# Use the 'optimize_steam_flow' function and other helper functions from the previous section
def create_gradio_interface(df):
def display_results():
result = optimize_steam_flow(df) # Call the Q-learning optimization
if result:
# Prepare bar chart data for recommended steam flows
recommended_steam_flows = list(result["recommended"].values())
sks = list(result["recommended"].keys())
# Plotly Bar Chart for Recommended Steam Flow per SK
bar_chart = go.Figure(data=[
go.Bar(
x=sks,
y=recommended_steam_flows,
marker_color='skyblue',
text=[f"{val:.2f} TPH" for val in recommended_steam_flows],
textposition='auto'
)
])
bar_chart.update_layout(
title='Recommended Steam Flow per SK (TPH)',
xaxis_title='SK',
yaxis_title='Steam Flow (TPH)',
template='plotly_dark',
plot_bgcolor='#121212',
paper_bgcolor='#121212',
font=dict(color='white'),
margin=dict(l=50, r=50, t=50, b=50)
)
# Prepare Comparison Chart (Current vs. Recommended Total Steam Flow)
total_steam_flow = result["current_total_steam_flow"]
recommended_total_steam_flow = result["recommended_total_steam_flow"]
# Plotly Comparison Bar Chart for Total Steam Flow
comparison_chart = go.Figure(data=[
go.Bar(
x=['Current', 'Recommended'],
y=[total_steam_flow, recommended_total_steam_flow],
marker_color=['orange', 'lightgreen'],
text=[f"{total_steam_flow:.2f} TPH", f"{recommended_total_steam_flow:.2f} TPH"],
textposition='auto'
)
])
comparison_chart.update_layout(
title='Total Steam Flow Comparison (TPH)',
xaxis_title='Steam Flow Type',
yaxis_title='Steam Flow (TPH)',
template='plotly_dark',
plot_bgcolor='#121212',
paper_bgcolor='#121212',
font=dict(color='white'),
margin=dict(l=50, r=50, t=50, b=50)
)
# Stylish Steam Flow Summary
summary = {
"Current Total Steam Flow (TPH)": f"{total_steam_flow:.2f}",
"Recommended Total Steam Flow (TPH)": f"{recommended_total_steam_flow:.2f}",
"Recommended Steam Flows for SKs (TPH)": {k: f"{v:.2f}" for k, v in result["recommended"].items()}
}
# Custom HTML to style the summary in a visually appealing way with white text
summary_html = f"""
<div style="font-size: 18px; color: white; background-color: #333; padding: 20px; border-radius: 10px;">
<h3 style="color: #ffcc00;">Steam Flow Summary</h3>
<p><strong style="color: white;">Current Total Steam Flow (TPH):</strong> <span style="color: #ffcc00;">{summary["Current Total Steam Flow (TPH)"]} TPH</span></p>
<p><strong style="color: white;">Recommended Total Steam Flow (TPH):</strong> <span style="color: #ffcc00;">{summary["Recommended Total Steam Flow (TPH)"]} TPH</span></p>
<p><strong style="color: white;">Recommended Steam Flows for SKs (TPH):</strong></p>
<ul style="padding-left: 20px;">
{"".join([f"<li style='color: white;'>{sk}: <span style='color: #ffcc00;'>{val} TPH</span></li>" for sk, val in summary["Recommended Steam Flows for SKs (TPH)"].items()])}
</ul>
</div>
"""
return bar_chart, comparison_chart, summary_html
# Create Gradio interface
with gr.Blocks() as demo:
# Title with custom styling
gr.Markdown(
"<h1 style='color: #ffcc00; font-weight: bold; text-align: left;'>Steam Flow Optimization Dashboard</h1>",
elem_id="title"
)
# Create charts row
with gr.Row():
bar_chart_output = gr.Plot(label="Recommended Steam Flow per SK")
comparison_chart_output = gr.Plot(label="Total Steam Flow Comparison")
# Steam flow summary section placed after the graphs
with gr.Row():
with gr.Column():
steam_flow_summary = gr.HTML(label="**Steam Flow Summary**")
# Optimize button at the bottom with enhanced styling
optimize_button = gr.Button("Optimize Steam Flow", elem_id="optimize_button", size="lg", variant="primary")
# Connect button to function
optimize_button.click(display_results, outputs=[bar_chart_output, comparison_chart_output, steam_flow_summary])
# CSS for custom styling
demo.css = """
#title {
font-size: 30px;
color: #ffcc00;
font-weight: bold;
text-align: left;
padding-left: 20px;
}
#optimize_button {
margin-top: 30px;
background-color: #4CAF50;
color: white;
border-radius: 10px;
padding: 15px;
width: 200px;
font-size: 18px;
}
.gradio-container {
background-color: #1e1e1e;
color: white;
border-radius: 15px;
padding: 30px;
margin: 50px;
}
.gradio-json {
background-color: #333;
color: white;
border-radius: 10px;
padding: 20px;
}
.gradio-html {
font-size: 16px;
color: white;
background-color: #333;
padding: 20px;
border-radius: 10px;
}
"""
return demo
# Create and launch the Gradio app
gradio_app = create_gradio_interface(df)
gradio_app.launch(share=True)