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"", 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(""" """, 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"
{alert}
", 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(""" ABC Classification categorizes inventory based on value and quantity to prioritize management efforts:
- Class A: High-value, low-quantity items (e.g., Smartphones, Laptops) requiring tight control.
- Class B: Medium-value, medium-quantity items (e.g., Headphones) needing balanced oversight.
- Class C: Low-value, high-quantity items (e.g., Smartwatches) requiring minimal attention.
Example: 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(""" - Low Risk: Risk below 15%. Indicates the current stock is sufficient with no immediate action needed.
- Medium Risk: Risk between 15% and 40%. Suggests monitoring the product closely to avoid potential stockouts.
- High Risk: Risk above 40%. Indicates a need to replenish inventory immediately to prevent stockouts. """, unsafe_allow_html=True) with st.expander("Inventory Levels"): st.markdown(""" - Optimal Inventory Level: Stock is well above safety stock, ensuring availability without excess.
- Monitor Closely: Stock is nearing safety stock; keep an eye on demand and supply.
- Replenish Inventory: Stock is below safety stock; urgent action is required to restock. """, unsafe_allow_html=True) with st.expander("Benefits and Use Cases"): st.markdown(""" Benefits include:
- Cost Savings: Reduces excess inventory and storage costs.
- Efficiency: Automates tracking, saving time.
- Customer Satisfaction: Prevents stockouts, ensuring product availability.
Use Cases:
- Retail: Manage stock in electronics stores or e-commerce.
- Manufacturing: Track electronic components and finished goods.
- Healthcare: Ensure availability of medical electronics. """, unsafe_allow_html=True) display_footer()