File size: 15,246 Bytes
4c1fca8 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 |
import streamlit as st
import pandas as pd
import logging
from database import get_db, initialize_database, Product
from inventory import (add_product, get_all_products, get_product_by_id, add_product_memory,
update_product_memory, delete_product_memory, add_stock_memory,
remove_stock_memory)
from prediction import predict_stockout
logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s")
logger = logging.getLogger(__name__)
st.set_page_config(page_title="Inventory Dashboard", layout="wide")
try:
with open("styles.css") as f:
st.markdown(f"<style>{f.read()}</style>", unsafe_allow_html=True)
except FileNotFoundError:
logger.warning("styles.css not found")
# Initialize session state
if "user_products" not in st.session_state:
st.session_state.user_products = []
if "user_transactions" not in st.session_state:
st.session_state.user_transactions = []
# Initialize example products
def initialize_example_products(db):
"""Add electronics example products if database is empty."""
try:
if db.query(Product).count() == 0:
examples = [
("Smartphone", 50.0, 20.0, 7.0, 200.0, "A"), # Electronics
("Laptop", 30.0, 10.0, 5.0, 150.0, "A"), # Electronics
("Headphones", 100.0, 30.0, 3.0, 120.0, "B"), # Electronics
("Smartwatch", 80.0, 25.0, 4.0, 90.0, "C") # Electronics
]
for name, stock, safety, lead, demand, cls in examples:
add_product(db, name, stock, safety, lead, demand, cls)
logger.info("Added example electronics products")
except Exception as e:
logger.error(f"Error adding example products: {str(e)}")
raise
# Initialize database and example products
try:
initialize_database() # Ensure tables are created
with next(get_db()) as db:
initialize_example_products(db)
cached_products = get_all_products(db) # Cache for efficiency
except Exception as e:
logger.error(f"Error initializing database or example products: {str(e)}")
st.error(f"Error: {str(e)}")
st.stop()
# Navigation
st.sidebar.title("Navigation")
page = st.sidebar.radio("Go to", ["Dashboard", "Product Management", "User Guide"])
st.sidebar.markdown("**Note**: Changes are local to your session and reset on page refresh.")
# Footer
def display_footer():
st.markdown("""
<div class="footer">
<p style="font-size: small; color: grey; text-align: center;">
Developed By: Krishna Prakash
<a href="https://www.linkedin.com/in/krishnaprakash-profile/" target="_blank">
<img src="https://img.icons8.com/ios-filled/30/0077b5/linkedin.png" alt="LinkedIn" style="vertical-align: middle; margin: 0 10px;"/>
</a>
</p>
</div>
""", unsafe_allow_html=True)
# Validate inputs
def validate_product_inputs(name, current_stock, safety_stock, lead_time, monthly_demand, product_class):
if not name:
return "Name is required."
if any(x < 0 for x in [current_stock, safety_stock, lead_time, monthly_demand]):
return "Negative values not allowed."
if product_class not in ["A", "B", "C"]:
return "Product class must be A, B, or C."
return None
# Dashboard
if page == "Dashboard":
st.title("Inventory Dashboard")
try:
session_products = st.session_state.user_products
# Cache all_products to avoid repeated computation
all_products = cached_products + [p for p in session_products]
if not all_products:
st.warning("No products found.")
else:
product_data = [{
"ID": p.id if hasattr(p, "id") else p.get("id", "N/A"),
"Name": p.name if hasattr(p, "name") else p.get("name", "Unknown"),
"Stock": p.current_stock if hasattr(p, "current_stock") else p.get("current_stock", 0.0),
"Safety Stock": p.safety_stock if hasattr(p, "safety_stock") else p.get("safety_stock", 0.0),
"Lead Time (days)": p.lead_time if hasattr(p, "lead_time") else p.get("lead_time", 0.0),
"Demand": p.monthly_demand if hasattr(p, "monthly_demand") else p.get("monthly_demand", 0.0),
"Class": p.product_class if hasattr(p, "product_class") else p.get("product_class", "C"),
"Source": "Example" if hasattr(p, "id") else "User"
} for p in all_products]
# Reset index to remove Sl. No. and keep ID
df = pd.DataFrame(product_data).reset_index(drop=True)
st.subheader("Inventory")
st.dataframe(df, use_container_width=True)
st.subheader("Stockout Predictions")
for p in all_products:
with st.expander(f"{p.name if hasattr(p, 'name') else p.get('name', 'Unknown')} (ID: {p.id if hasattr(p, 'id') else p.get('id', 'N/A')})"):
result = predict_stockout(
p.monthly_demand if hasattr(p, "monthly_demand") else p.get("monthly_demand", 0.0),
p.lead_time if hasattr(p, "lead_time") else p.get("lead_time", 0.0),
p.current_stock if hasattr(p, "current_stock") else p.get("current_stock", 0.0),
p.product_class if hasattr(p, "product_class") else p.get("product_class", "C")
)
st.markdown(result, unsafe_allow_html=True)
st.subheader("Alerts")
alerts = []
for p in all_products:
stock = p.current_stock if hasattr(p, "current_stock") else p.get("current_stock", 0.0)
safety = p.safety_stock if hasattr(p, "safety_stock") else p.get("safety_stock", 0.0)
name = p.name if hasattr(p, "name") else p.get("name", "Unknown")
# Use prediction to determine alert
result = predict_stockout(
p.monthly_demand if hasattr(p, "monthly_demand") else p.get("monthly_demand", 0.0),
p.lead_time if hasattr(p, "lead_time") else p.get("lead_time", 0.0),
stock,
p.product_class if hasattr(p, "product_class") else p.get("product_class", "C")
)
risk_level = "Low Risk" if "Low Risk" in result else "Medium Risk" if "Medium Risk" in result else "High Risk"
if "High Risk" in risk_level:
alerts.append(f"High Risk Alert: {name} has {stock} units. Replenish inventory immediately.")
elif "Medium Risk" in risk_level:
alerts.append(f"Medium Risk Alert: {name} has {stock} units. Monitor closely.")
elif "Low Risk" in risk_level and stock < safety:
alerts.append(f"Low Risk Alert: {name} has {stock} units, below {safety}. No action needed yet.")
if alerts:
for alert in alerts:
alert_class = "alert-high" if "High Risk" in alert else "alert-medium" if "Medium Risk" in alert else "alert"
st.markdown(f"<div class='{alert_class}'>{alert}</div>", unsafe_allow_html=True)
else:
st.success("All products are above predicted safety thresholds.")
except Exception as e:
logger.error(f"Dashboard error: {str(e)}")
st.error(f"Error: {str(e)}")
# Product Management
elif page == "Product Management":
st.title("Product Management")
st.markdown("**Note**: Products and changes are local to your session and reset on page refresh.")
try:
st.subheader("Add Product")
with st.form("add_form"):
name = st.text_input("Name")
current_stock = st.number_input("Stock", min_value=0.0, step=1.0)
safety_stock = st.number_input("Safety Stock", min_value=0.0, step=1.0)
lead_time = st.number_input("Lead Time (days)", min_value=0.0, max_value=90.0, step=1.0)
monthly_demand = st.number_input("Monthly Demand", min_value=0.0, step=1.0)
product_class = st.selectbox("Class", ["A", "B", "C"])
if st.form_submit_button("Add"):
error = validate_product_inputs(name, current_stock, safety_stock, lead_time, monthly_demand, product_class)
if error:
st.error(error)
else:
add_product_memory(st.session_state.user_products, name, current_stock, safety_stock, lead_time, monthly_demand, product_class)
st.success(f"Added {name} (local to your session)")
st.subheader("Manage Products")
session_products = st.session_state.user_products
if session_products:
product_id = st.selectbox("Select Product", [p["id"] for p in session_products], format_func=lambda x: next(p["name"] for p in session_products if p["id"] == x))
product = next(p for p in session_products if p["id"] == product_id)
with st.form("edit_form"):
edit_name = st.text_input("Name", value=product["name"])
edit_current_stock = st.number_input("Stock", min_value=0.0, step=1.0, value=float(product["current_stock"]))
edit_safety_stock = st.number_input("Safety Stock", min_value=0.0, step=1.0, value=float(product["safety_stock"]))
edit_lead_time = st.number_input("Lead Time (days)", min_value=0.0, max_value=90.0, step=1.0, value=float(product["lead_time"]))
edit_monthly_demand = st.number_input("Monthly Demand", min_value=0.0, step=1.0, value=float(product["monthly_demand"]))
edit_product_class = st.selectbox("Class", ["A", "B", "C"], index=["A", "B", "C"].index(product["product_class"]))
col1, col2, col3, col4 = st.columns(4)
with col1:
if st.form_submit_button("Update"):
error = validate_product_inputs(edit_name, edit_current_stock, edit_safety_stock, edit_lead_time, edit_monthly_demand, edit_product_class)
if error:
st.error(error)
else:
update_product_memory(st.session_state.user_products, product_id, edit_name, edit_current_stock, edit_safety_stock, edit_lead_time, edit_monthly_demand, edit_product_class)
st.success(f"Updated {edit_name} (local)")
with col2:
if st.form_submit_button("Delete"):
delete_product_memory(st.session_state.user_products, product_id)
st.success(f"Deleted {product['name']} (local)")
with col3:
add_qty = st.number_input("Add Stock", min_value=0.0, step=1.0, key="add_qty")
if st.form_submit_button("Add Stock"):
if add_qty <= 0:
st.error("Quantity must be positive.")
else:
add_stock_memory(st.session_state.user_products, st.session_state.user_transactions, product_id, add_qty)
st.success(f"Added {add_qty} to {product['name']} (local)")
with col4:
remove_qty = st.number_input("Remove Stock", min_value=0.0, step=1.0, key="remove_qty")
if st.form_submit_button("Remove Stock"):
if remove_qty <= 0:
st.error("Quantity must be positive.")
else:
remove_stock_memory(st.session_state.user_products, st.session_state.user_transactions, product_id, remove_qty)
st.success(f"Removed {remove_qty} from {product['name']} (local)")
else:
st.warning("No user-added products in your session.")
except Exception as e:
logger.error(f"Product management error: {str(e)}")
st.error(f"Error: {str(e)}")
# User Guide
elif page == "User Guide":
st.title("User Guide")
st.markdown("""
Welcome to the **Inventory Dashboard**! This guide explains how to use the system, the meaning of risk levels, and inventory management concepts.
""")
with st.expander("What is the Inventory Dashboard?"):
st.markdown("""
The Inventory Dashboard helps you track and manage stock levels for electronics products. It predicts stockout risks and provides alerts to ensure availability.
""")
with st.expander("ABC Classification"):
st.markdown("""
<b>ABC Classification</b> categorizes inventory based on value and quantity to prioritize management efforts:<br>
- <b>Class A</b>: High-value, low-quantity items (e.g., Smartphones, Laptops) requiring tight control.<br>
- <b>Class B</b>: Medium-value, medium-quantity items (e.g., Headphones) needing balanced oversight.<br>
- <b>Class C</b>: Low-value, high-quantity items (e.g., Smartwatches) requiring minimal attention.<br>
<b>Example</b>: A store might prioritize restocking Smartphones (Class A) over Smartwatches (Class C) due to higher value and demand variability.
""", unsafe_allow_html=True)
with st.expander("Risk Levels"):
st.markdown("""
- <b>Low Risk</b>: Risk below 15%. Indicates the current stock is sufficient with no immediate action needed.<br>
- <b>Medium Risk</b>: Risk between 15% and 40%. Suggests monitoring the product closely to avoid potential stockouts.<br>
- <b>High Risk</b>: Risk above 40%. Indicates a need to replenish inventory immediately to prevent stockouts.
""", unsafe_allow_html=True)
with st.expander("Inventory Levels"):
st.markdown("""
- <b>Optimal Inventory Level</b>: Stock is well above safety stock, ensuring availability without excess.<br>
- <b>Monitor Closely</b>: Stock is nearing safety stock; keep an eye on demand and supply.<br>
- <b>Replenish Inventory</b>: Stock is below safety stock; urgent action is required to restock.
""", unsafe_allow_html=True)
with st.expander("Benefits and Use Cases"):
st.markdown("""
<b>Benefits include:</b><br>
- Cost Savings: Reduces excess inventory and storage costs.<br>
- Efficiency: Automates tracking, saving time.<br>
- Customer Satisfaction: Prevents stockouts, ensuring product availability.<br>
<b>Use Cases:</b><br>
- Retail: Manage stock in electronics stores or e-commerce.<br>
- Manufacturing: Track electronic components and finished goods.<br>
- Healthcare: Ensure availability of medical electronics.
""", unsafe_allow_html=True)
display_footer() |