Commit a62d40a8 authored by Barthelet Thibault's avatar Barthelet Thibault
Browse files

applied review

parent adf80d58
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -12,7 +12,7 @@ Faire retailer & Warehouse
Forecast Accuracy -> En valeur "IA" et moins Supply Chain. Genre "ML Prediction Accuracy"

Juste MAPE (sans "Forecast")
![alt text](image.png)
![](image.png)

System Cost -> Logistics Costs ? Idée de tous les coûts associés à la supply chain

+22 −9
Original line number Diff line number Diff line
@@ -18,7 +18,7 @@ class CustomerBehavior:
    def sample_customers_for_timestep(self, n_customers: int = None) -> List[Customer]:
        """Sample customers who will shop in this timestep."""
        if n_customers is None:
            # Sample 10-20% of customers per timestep (increased from 5-10%)
            # Sample 10-20% of customers per timestep
            n_customers = max(10, int(len(self.setup.customers) * np.random.uniform(0.1, 0.2)))
        
        sampled = np.random.choice(self.setup.customers, min(n_customers, len(self.setup.customers)), replace=False)
@@ -48,11 +48,21 @@ class CustomerBehavior:
        if not watch:
            return 0
        
        # Get retailer to calculate final price
        retailer = next((r for r in self.setup.retailers if r.region == customer.region), 
                        self.setup.retailers[0] if self.setup.retailers else None)
        
        if not retailer:
            return 0
        
        # Calculate retail price (sell_price * retailer markup)
        retail_price = watch.sell_price * retailer.markup_policy
        
        # Simplified purchase probability calculation
        match_score = self.compute_preference_match(customer, watch_id)
        
        # Budget fit - much more lenient
        budget_fit = 1.0 if watch.base_price <= customer.budget else max(0.1, customer.budget / watch.base_price)
        budget_fit = 1.0 if retail_price <= customer.budget else max(0.1, customer.budget / retail_price)
        
        # Brand awareness factor
        brand_awareness = min(1.0, customer.brand_awareness + 0.3)  # Base awareness boost
@@ -95,7 +105,7 @@ class CustomerBehavior:
            # Choose a retailer to visit
            retailer = np.random.choice(region_retailers)
            
            # Get available watches at this retailer
            # Get available watches at this retailer (stock quantity > 0)
            available_watches = [(inv.watch_id, inv.quantity) 
                               for inv in retailer_inventories 
                               if inv.retailer_id == retailer.id and inv.quantity > 0]
@@ -104,8 +114,8 @@ class CustomerBehavior:
                continue
            
            # Try to buy something - check each available watch
            for watch_id, quantity in available_watches:
                if quantity <= 0:
            for watch_id, stock_quantity in available_watches:
                if stock_quantity <= 0:
                    continue
                    
                prob = self.evaluate_purchase_probability(customer, watch_id, event_modifiers)
@@ -115,6 +125,9 @@ class CustomerBehavior:
                    watch = next((w for w in self.setup.watches if w.id == watch_id), None)
                    
                    if watch:
                        # Calculate final retail price (customer pays this)
                        retail_price = watch.sell_price * retailer.markup_policy
                        
                        # Create sale
                        sale = Sale(
                            id=self.sale_id_counter,
@@ -122,7 +135,7 @@ class CustomerBehavior:
                            retailer_id=retailer.id,
                            watch_id=watch_id,
                            quantity=1,
                            total_price=watch.base_price * retailer.markup_policy,
                            total_price=retail_price,
                            date=datetime.now()
                        )
                        
@@ -130,12 +143,12 @@ class CustomerBehavior:
                        self.sales.append(sale)
                        self.sale_id_counter += 1
                        
                        # Deduct from inventory
                        # Deduct from stock
                        key = (retailer.id, watch_id)
                        if key in inv_map:
                            inv_map[key].quantity -= 1
                        
                        logger.debug(f"Customer {customer.id} purchased watch {watch_id} for ${sale.total_price:.0f}")
                        logger.debug(f"Customer {customer.id} purchased watch {watch_id} for ${retail_price:.0f}")
                        break  # Customer only buys one watch per visit
        
        logger.info(f"Processed {len(new_sales)} sales")
@@ -152,7 +165,7 @@ class CustomerBehavior:
        for sale in recent_sales:
            # Use a simple 5% return rate
            if np.random.random() < 0.05:
                # Process return
                # Process return - add back to stock
                key = (sale.retailer_id, sale.watch_id)
                if key in inv_map:
                    inv_map[key].quantity += sale.quantity
+47 −19
Original line number Diff line number Diff line
@@ -50,7 +50,7 @@ class RetailDistribution:
                for watch_id, demand in region_demand.items():
                    allocated_qty = int(demand * retailer_share)
                    
                    # Respect capacity constraints
                    # Respect capacity constraints (stock capacity)
                    current_load = sum(allocation[retailer.id].values())
                    available_capacity = retailer.capacity - current_load
                    
@@ -91,13 +91,13 @@ class RetailDistribution:
                if watch_id not in warehouse_watches:
                    continue
                
                # Find available stock
                available_qty = sum(inv.quantity for inv in warehouse_watches[watch_id])
                # Find available stock in warehouse
                available_stock = sum(inv.quantity for inv in warehouse_watches[watch_id])
                
                if available_qty > 0:
                    to_ship = min(required_qty, available_qty)
                if available_stock > 0:
                    to_ship = min(required_qty, available_stock)
                    
                    # Deduct from warehouses
                    # Deduct from warehouse stock
                    remaining = to_ship
                    for w_inv in warehouse_watches[watch_id]:
                        if remaining <= 0:
@@ -107,7 +107,7 @@ class RetailDistribution:
                        w_inv.quantity -= taken
                        remaining -= taken
                    
                    # Add to retailer
                    # Add to retailer stock
                    key = (retailer_id, watch_id)
                    if key in retailer_inv_map:
                        retailer_inv_map[key].quantity += to_ship
@@ -134,9 +134,9 @@ class RetailDistribution:
        return warehouse_inventories, retailer_inventories
    
    def calculate_distribution_costs(self) -> float:
        """Calculate total distribution costs."""
        """Calculate total distribution/logistics costs."""
        # Simplified cost model
        base_cost_per_unit = 5.0
        base_cost_per_unit = 5.0  # $5 per unit shipped
        total_cost = 0
        
        for dist in self.distribution_history:
@@ -144,16 +144,44 @@ class RetailDistribution:
        
        return total_cost
    
    def get_retailer_fill_rates(self, retailer_inventories: List[RetailerInventory]) -> Dict[int, float]:
        """Calculate fill rates for each retailer."""
        fill_rates = {}
    def get_retailer_capacity_utilization(self, retailer_inventories: List[RetailerInventory]) -> Dict[int, float]:
        """Calculate capacity utilization for each retailer.
        Capacity utilization = Current Stock / Total Capacity
        """
        capacity_utilization = {}
        
        # Group stock by retailer
        retailer_stock = {}
        for inv in retailer_inventories:
            if inv.retailer_id not in retailer_stock:
                retailer_stock[inv.retailer_id] = 0
            retailer_stock[inv.retailer_id] += inv.quantity
        
        # Calculate utilization for each retailer
        for retailer in self.setup.retailers:
            retailer_inv = [inv for inv in retailer_inventories if inv.retailer_id == retailer.id]
            current_stock = retailer_stock.get(retailer.id, 0)
            utilization = current_stock / retailer.capacity if retailer.capacity > 0 else 0
            capacity_utilization[retailer.id] = min(1.0, utilization)  # Cap at 100%
        
        return capacity_utilization
    
            total_stock = sum(inv.quantity for inv in retailer_inv)
            fill_rate = min(1.0, total_stock / retailer.capacity) if retailer.capacity > 0 else 0
    def get_warehouse_capacity_utilization(self, warehouse_inventories: List[WarehouseInventory]) -> Dict[int, float]:
        """Calculate capacity utilization for each warehouse.
        Capacity utilization = Current Stock / Total Capacity
        """
        capacity_utilization = {}
        
        # Group stock by warehouse
        warehouse_stock = {}
        for inv in warehouse_inventories:
            if inv.warehouse_id not in warehouse_stock:
                warehouse_stock[inv.warehouse_id] = 0
            warehouse_stock[inv.warehouse_id] += inv.quantity
        
            fill_rates[retailer.id] = fill_rate
        # Calculate utilization for each warehouse
        for warehouse in self.setup.warehouses:
            current_stock = warehouse_stock.get(warehouse.id, 0)
            utilization = current_stock / warehouse.capacity if warehouse.capacity > 0 else 0
            capacity_utilization[warehouse.id] = min(1.0, utilization)  # Cap at 100%
        
        return fill_rates
 No newline at end of file
        return capacity_utilization
 No newline at end of file
+7 −6
Original line number Diff line number Diff line
@@ -28,7 +28,7 @@ class ProductComponent:
    id: int
    name: str
    type: str  # movement, case, strap, etc
    cost: float
    cost: float  # Purchase cost from supplier
    storage_rate: float  # cost per unit per day
    shelf_life: int  # days
    quality_tier: int  # 1-5
@@ -48,7 +48,7 @@ class WarehouseInventory:
    warehouse_id: int
    item_id: int
    item_type: str  # "component" or "watch"
    quantity: int
    quantity: int  # Stock quantity
    age: int = 0  # days

@dataclass
@@ -57,7 +57,8 @@ class WatchModel:
    brand_id: int
    name: str
    category: str  # luxury, sport, casual
    base_price: float
    base_cost: float  # Manufacturing cost (sum of components + labor)
    sell_price: float = 0  # Selling price to retailers (base_cost * markup)

@dataclass
class WatchBOM:
@@ -72,14 +73,14 @@ class Retailer:
    name: str
    region: str
    capacity: int
    markup_policy: float  # multiplier
    markup_policy: float  # multiplier for retail price

@dataclass
class RetailerInventory:
    id: int
    retailer_id: int
    watch_id: int
    quantity: int
    quantity: int  # Stock quantity
    return_rate: float = 0.05

@dataclass
@@ -99,7 +100,7 @@ class Sale:
    retailer_id: int
    watch_id: int
    quantity: int
    total_price: float
    total_price: float  # Price paid by customer
    date: datetime

@dataclass
+1 −1
Original line number Diff line number Diff line
@@ -64,7 +64,7 @@ class Forecasting:
            base_demand = np.random.uniform(15, 30)  # Casual has higher volume
        
        # Price sensitivity - higher prices reduce demand
        price_factor = max(0.3, 1.0 - (watch.base_price - 200) / 2000)
        price_factor = max(0.3, 1.0 - (watch.base_cost - 200) / 2000)
        base_demand *= price_factor
        
        # Regional variation
Loading