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()