""" Inventory Management System for Equipment and Materials """ import json import math import datetime import dataclasses import hashlib from typing import Dict, List, Optional, Any, Tuple @dataclasses.dataclass class InventoryTransaction: transaction_id: str item_id: str transaction_type: str # "in", "out", "adjustment", "transfer" quantity: int unit_cost: float timestamp: datetime.datetime reference: str operator: str @dataclasses.dataclass class StockAlert: alert_id: str item_id: str alert_type: str # "low_stock", "out_of_stock", "overstock", "expired" severity: str # "low", "medium", "high", "critical" message: str timestamp: datetime.datetime resolved: bool = False @dataclasses.dataclass class Supplier: supplier_id: str name: str contact_info: Dict[str, str] lead_time_days: int reliability_score: float payment_terms: str items_supplied: List[str] class InventoryManagementSystem: """Comprehensive inventory management for all equipment""" def __init__(self, platform): self.platform = platform self.transactions: Dict[str, InventoryTransaction] = {} self.alerts: Dict[str, StockAlert] = {} self.suppliers: Dict[str, Supplier] = {} self.forecasts: Dict[str, Dict] = {} def add_item(self, item_id: str, name: str, category: str, quantity: int, location: str, min_threshold: int, unit_cost: float) -> Dict: """Add new item to inventory""" try: item = InventoryItem( item_id=item_id, name=name, category=category, quantity=quantity, location=location, min_threshold=min_threshold, last_updated=datetime.datetime.now(), unit_cost=unit_cost ) self.platform.inventory[item_id] = item # Check for initial alerts self._check_stock_alerts(item_id) # Create initial transaction transaction_id = self._generate_transaction_id() transaction = InventoryTransaction( transaction_id=transaction_id, item_id=item_id, transaction_type="in", quantity=quantity, unit_cost=unit_cost, timestamp=datetime.datetime.now(), reference="initial_stock", operator="system" ) self.transactions[transaction_id] = transaction return { "success": True, "item_id": item_id, "current_quantity": quantity, "transaction_id": transaction_id } except Exception as e: return {"success": False, "error": str(e)} def update_quantity(self, item_id: str, quantity_change: int, transaction_type: str, reference: str, operator: str = "system") -> Dict: """Update item quantity with transaction tracking""" if item_id not in self.platform.inventory: return {"error": "Item not found"} item = self.platform.inventory[item_id] old_quantity = item.quantity # Validate quantity change new_quantity = old_quantity + quantity_change if new_quantity < 0: return {"error": "Insufficient stock"} # Update quantity item.quantity = new_quantity item.last_updated = datetime.datetime.now() # Create transaction record transaction_id = self._generate_transaction_id() transaction = InventoryTransaction( transaction_id=transaction_id, item_id=item_id, transaction_type=transaction_type, quantity=quantity_change, unit_cost=item.unit_cost, timestamp=datetime.datetime.now(), reference=reference, operator=operator ) self.transactions[transaction_id] = transaction # Check for stock alerts self._check_stock_alerts(item_id) return { "success": True, "item_id": item_id, "old_quantity": old_quantity, "new_quantity": new_quantity, "transaction_id": transaction_id } def transfer_item(self, item_id: str, quantity: int, from_location: str, to_location: str, operator: str = "system") -> Dict: """Transfer item between locations""" if item_id not in self.platform.inventory: return {"error": "Item not found"} item = self.platform.inventory[item_id] if item.location != from_location: return {"error": f"Item not in {from_location}"} if item.quantity < quantity: return {"error": "Insufficient quantity for transfer"} # Update quantities and location item.quantity -= quantity item.last_updated = datetime.datetime.now() # Check if item exists in destination location dest_item_id = f"{item_id}_{to_location}" if dest_item_id in self.platform.inventory: dest_result = self.update_quantity( dest_item_id, quantity, "in", f"transfer_from_{from_location}", operator ) else: # Create new item in destination dest_result = self.add_item( dest_item_id, item.name, item.category, quantity, to_location, item.min_threshold, item.unit_cost ) # Create transfer transaction transaction_id = self._generate_transaction_id() transaction = InventoryTransaction( transaction_id=transaction_id, item_id=item_id, transaction_type="transfer", quantity=-quantity, unit_cost=item.unit_cost, timestamp=datetime.datetime.now(), reference=f"transfer_to_{to_location}", operator=operator ) self.transactions[transaction_id] = transaction return { "success": True, "transfer_id": transaction_id, "item_id": item_id, "quantity": quantity, "from_location": from_location, "to_location": to_location, "source_quantity": item.quantity, "dest_result": dest_result } def get_low_stock_items(self, location: str = None) -> List[Dict]: """Get items below minimum threshold""" low_stock = [] for item in self.platform.inventory.values(): if location and item.location != location: continue if item.quantity < item.min_threshold: low_stock.append({ "item_id": item.item_id, "name": item.name, "current_quantity": item.quantity, "min_threshold": item.min_threshold, "shortage": item.min_threshold - item.quantity, "location": item.location, "urgency": self._calculate_urgency(item) }) return sorted(low_stock, key=lambda x: x["urgency"], reverse=True) def generate_inventory_report(self, category: str = None, location: str = None) -> Dict: """Generate comprehensive inventory report""" items = list(self.platform.inventory.values()) # Filter by category and location if specified if category: items = [item for item in items if item.category == category] if location: items = [item for item in items if item.location == location] # Calculate statistics total_items = len(items) total_quantity = sum(item.quantity for item in items) total_value = sum(item.quantity * item.unit_cost for item in items) low_stock_count = len([item for item in items if item.quantity < item.min_threshold]) # Category breakdown categories = {} for item in items: if item.category not in categories: categories[item.category] = { "count": 0, "quantity": 0, "value": 0 } categories[item.category]["count"] += 1 categories[item.category]["quantity"] += item.quantity categories[item.category]["value"] += item.quantity * item.unit_cost # Location breakdown locations = {} for item in items: if item.location not in locations: locations[item.location] = { "count": 0, "quantity": 0, "value": 0 } locations[item.location]["count"] += 1 locations[item.location]["quantity"] += item.quantity locations[item.location]["value"] += item.quantity * item.unit_cost return { "timestamp": datetime.datetime.now().isoformat(), "summary": { "total_items": total_items, "total_quantity": total_quantity, "total_value": total_value, "low_stock_items": low_stock_count, "average_unit_cost": total_value / total_quantity if total_quantity > 0 else 0 }, "categories": categories, "locations": locations, "alerts": { "total_alerts": len(self.alerts), "unresolved_alerts": len([a for a in self.alerts.values() if not a.resolved]) } } def demand_forecasting(self, item_id: str, days_ahead: int = 30) -> Dict: """Generate demand forecast for item""" if item_id not in self.platform.inventory: return {"error": "Item not found"} # Get historical transactions for the item item_transactions = [ t for t in self.transactions.values() if t.item_id == item_id and t.transaction_type == "out" ] if not item_transactions: return { "item_id": item_id, "forecast": "no_historical_data", "recommendation": "Collect more usage data" } # Simple moving average forecast recent_transactions = sorted( item_transactions, key=lambda x: x.timestamp, reverse=True )[:30] # Last 30 transactions daily_usage = sum(t.quantity for t in recent_transactions) / 30 forecast_demand = daily_usage * days_ahead item = self.platform.inventory[item_id] current_stock = item.quantity reorder_point = item.min_threshold recommended_order = max(0, forecast_demand - current_stock + reorder_point) forecast = { "item_id": item_id, "forecast_period_days": days_ahead, "current_stock": current_stock, "forecast_demand": round(forecast_demand, 2), "daily_usage_avg": round(daily_usage, 2), "reorder_point": reorder_point, "recommended_order_quantity": round(recommended_order), "stockout_risk": self._calculate_stockout_risk(current_stock, forecast_demand), "generated_at": datetime.datetime.now().isoformat() } self.forecasts[item_id] = forecast return forecast def add_supplier(self, supplier_id: str, name: str, contact_info: Dict[str, str], lead_time_days: int, reliability_score: float, payment_terms: str) -> Dict: """Add new supplier to the system""" try: supplier = Supplier( supplier_id=supplier_id, name=name, contact_info=contact_info, lead_time_days=lead_time_days, reliability_score=reliability_score, payment_terms=payment_terms, items_supplied=[] ) self.suppliers[supplier_id] = supplier return { "success": True, "supplier_id": supplier_id, "name": name, "reliability_score": reliability_score } except Exception as e: return {"success": False, "error": str(e)} def _generate_transaction_id(self) -> str: """Generate unique transaction ID""" return hashlib.md5( f"{datetime.datetime.now().isoformat()}_{len(self.transactions)}".encode() ).hexdigest()[:12] def _check_stock_alerts(self, item_id: str): """Check and create stock alerts for item""" item = self.platform.inventory[item_id] # Remove existing resolved alerts for this item resolved_alerts = [ alert_id for alert_id, alert in self.alerts.items() if alert.item_id == item_id and alert.resolved ] for alert_id in resolved_alerts: del self.alerts[alert_id] # Check for new alerts if item.quantity == 0: self._create_alert(item_id, "out_of_stock", "critical", f"Item {item.name} is out of stock") elif item.quantity < item.min_threshold * 0.2: self._create_alert(item_id, "low_stock", "critical", f"Item {item.name} is critically low ({item.quantity} units)") elif item.quantity < item.min_threshold: self._create_alert(item_id, "low_stock", "medium", f"Item {item.name} is below minimum threshold ({item.quantity} units)") elif item.quantity > item.min_threshold * 5: self._create_alert(item_id, "overstock", "low", f"Item {item.name} is overstocked ({item.quantity} units)") def _create_alert(self, item_id: str, alert_type: str, severity: str, message: str): """Create new stock alert""" alert_id = hashlib.md5( f"{item_id}_{alert_type}_{datetime.datetime.now().isoformat()}".encode() ).hexdigest()[:10] alert = StockAlert( alert_id=alert_id, item_id=item_id, alert_type=alert_type, severity=severity, message=message, timestamp=datetime.datetime.now() ) self.alerts[alert_id] = alert def _calculate_urgency(self, item: InventoryItem) -> float: """Calculate urgency score for low stock items""" if item.quantity == 0: return 1.0 shortage_ratio = (item.min_threshold - item.quantity) / item.min_threshold return max(0.0, min(1.0, shortage_ratio)) def _calculate_stockout_risk(self, current_stock: int, forecast_demand: float) -> str: """Calculate stockout risk based on current stock and forecast""" if current_stock <= 0: return "critical" elif current_stock < forecast_demand * 0.3: return "high" elif current_stock < forecast_demand * 0.7: return "medium" else: return "low" # Import required classes import hashlib from supply_chain_platform import InventoryItem