Loading HorloML_EDU_Main/demo_prediction_phase.py +1 −1 Original line number Diff line number Diff line Loading @@ -43,7 +43,7 @@ def demo_basic_scenario(): print("\nProducts in simulation:") for watch in setup.watches: print(f" • Watch ID {watch.id}: {watch.name} ({watch.category})") print(f" Cost: ${watch.base_cost:.0f}, Price: ${watch.sell_price:.0f}") print(f" Cost: CHF {watch.base_cost:.0f}, Price: CHF {watch.sell_price:.0f}") # Student makes predictions for each product, each month print("\n[PREDICTION PHASE] Student enters predictions...") Loading HorloML_EDU_Main/web_app/templates/predict.html +10 −10 Original line number Diff line number Diff line Loading @@ -68,8 +68,8 @@ {{ product.category|capitalize }} </span> </td> <td class="has-text-right">${{ "%.0f"|format(product.base_cost) }}</td> <td class="has-text-right">${{ "%.0f"|format(product.sell_price) }}</td> <td class="has-text-right">CHF {{ "%.0f"|format(product.base_cost) }}</td> <td class="has-text-right">CHF {{ "%.0f"|format(product.sell_price) }}</td> <td class="has-text-right">{{ "%.0f"|format(product.margin_pct) }}%</td> </tr> {% endfor %} Loading Loading @@ -192,35 +192,35 @@ <script> // Helper functions for quick-fill buttons function fillConstant(productId, nMonths) { const baseValue = document.getElementById(`pred_${productId}_1`).value || 10; const baseValue = document.getElementById(`pred_CHF {productId}_1`).value || 10; for (let month = 1; month <= nMonths; month++) { document.getElementById(`pred_${productId}_${month}`).value = baseValue; document.getElementById(`pred_CHF {productId}_CHF {month}`).value = baseValue; } } function fillIncreasing(productId, nMonths) { const baseValue = parseFloat(document.getElementById(`pred_${productId}_1`).value) || 10; const baseValue = parseFloat(document.getElementById(`pred_CHF {productId}_1`).value) || 10; const increment = 2; for (let month = 1; month <= nMonths; month++) { document.getElementById(`pred_${productId}_${month}`).value = Math.round(baseValue + (month - 1) * increment); document.getElementById(`pred_CHF {productId}_CHF {month}`).value = Math.round(baseValue + (month - 1) * increment); } } function fillDecreasing(productId, nMonths) { const baseValue = parseFloat(document.getElementById(`pred_${productId}_1`).value) || 20; const baseValue = parseFloat(document.getElementById(`pred_CHF {productId}_1`).value) || 20; const decrement = 1.5; for (let month = 1; month <= nMonths; month++) { const value = Math.max(1, Math.round(baseValue - (month - 1) * decrement)); document.getElementById(`pred_${productId}_${month}`).value = value; document.getElementById(`pred_CHF {productId}_CHF {month}`).value = value; } } function fillSeasonal(productId, nMonths) { const baseValue = parseFloat(document.getElementById(`pred_${productId}_1`).value) || 15; const baseValue = parseFloat(document.getElementById(`pred_CHF {productId}_1`).value) || 15; for (let month = 1; month <= nMonths; month++) { // Simple sine wave pattern const seasonal = baseValue + Math.sin(month * Math.PI / 6) * (baseValue * 0.3); document.getElementById(`pred_${productId}_${month}`).value = Math.round(Math.max(1, seasonal)); document.getElementById(`pred_CHF {productId}_CHF {month}`).value = Math.round(Math.max(1, seasonal)); } } Loading supply_chain_sim/cmd_visual.py +24 −24 Original line number Diff line number Diff line Loading @@ -176,8 +176,8 @@ class VisualSimulation(SupplyChainSimulation): watch_table.add_row( f"Watch_{watch.id}", watch.category.capitalize(), f"${watch.base_cost:.0f}", f"${watch.sell_price:.0f}", f"CHF {watch.base_cost:.0f}", f"CHF {watch.sell_price:.0f}", f"{markup:.0f}%", ) Loading Loading @@ -329,7 +329,7 @@ class VisualSimulation(SupplyChainSimulation): f"Comp_{order.component_id}", str(current), str(order.quantity), f"${order.cost:.0f}", f"CHF {order.cost:.0f}", f"{order.expected_delivery:.1f}d", ) Loading @@ -344,7 +344,7 @@ class VisualSimulation(SupplyChainSimulation): ) ) log_to_file( f"Generated {len(supply_orders)} supply orders, total cost: ${sum(o.cost for o in supply_orders):.0f}" f"Generated {len(supply_orders)} supply orders, total cost: CHF {sum(o.cost for o in supply_orders):.0f}" ) phase_results.append(("Orders", len(supply_orders))) else: Loading Loading @@ -518,15 +518,15 @@ class VisualSimulation(SupplyChainSimulation): console.print( f" [green]✓ {len(new_sales)} sales from {len(sampled_customers)} visitors (conversion: {conversion:.1f}%)[/green]" ) console.print(f" [green]✓ Revenue: ${total_revenue:,.0f}[/green]") console.print(f" [green]✓ Revenue: CHF {total_revenue:,.0f}[/green]") log_to_file( f"{len(new_sales)} sales from {len(sampled_customers)} visitors (conversion: {conversion:.1f}%), Revenue: ${total_revenue:,.0f}" f"{len(new_sales)} sales from {len(sampled_customers)} visitors (conversion: {conversion:.1f}%), Revenue: CHF {total_revenue:,.0f}" ) sales_breakdown = [] for cat, data in sales_by_category.items(): sales_breakdown.append( f"{cat}: {data['count']} (${data['revenue']:,.0f})" f"{cat}: {data['count']} (CHF {data['revenue']:,.0f})" ) breakdown_str = ", ".join(sales_breakdown) console.print(f" [dim]Sales by category: {breakdown_str}[/dim]") Loading Loading @@ -609,13 +609,13 @@ class VisualSimulation(SupplyChainSimulation): "GMROI", f"{kpis['gmroi']:.2f}", "Total Revenue", f"${kpis['total_revenue']:,.0f}", f"CHF {kpis['total_revenue']:,.0f}", ) kpi_grid.add_row( "Gross Margin", f"${kpis['gross_margin']:,.0f}", f"CHF {kpis['gross_margin']:,.0f}", "Logistics Costs", f"${kpis['logistics_costs']:,.0f}", f"CHF {kpis['logistics_costs']:,.0f}", ) kpi_grid.add_row( "Warehouse Util", Loading @@ -626,7 +626,7 @@ class VisualSimulation(SupplyChainSimulation): console.print(Panel(kpi_grid, title="Daily KPIs", border_style="green")) log_to_file( f"Daily KPIs - GMROI: {kpis['gmroi']:.2f}, Revenue: ${kpis['total_revenue']:,.0f}, Margin: ${kpis['gross_margin']:,.0f}, Warehouse Util: {kpis['warehouse_capacity_utilization']:.1%}, Retailer Util: {kpis['retailer_capacity_utilization']:.1%}" f"Daily KPIs - GMROI: {kpis['gmroi']:.2f}, Revenue: CHF {kpis['total_revenue']:,.0f}, Margin: CHF {kpis['gross_margin']:,.0f}, Warehouse Util: {kpis['warehouse_capacity_utilization']:.1%}, Retailer Util: {kpis['retailer_capacity_utilization']:.1%}" ) # Store metrics for final report Loading Loading @@ -668,7 +668,7 @@ class VisualSimulation(SupplyChainSimulation): # Executive Summary exec_summary = f""" [bold cyan]Simulation Period:[/bold cyan] {self.config['simulation_days']} days [bold cyan]Total Revenue:[/bold cyan] ${total_revenue:,.0f} [bold cyan]Total Revenue:[/bold cyan] CHF {total_revenue:,.0f} [bold cyan]Total Sales:[/bold cyan] {total_sales} units [bold cyan]Customers:[/bold cyan] {len(self.setup.customers)} potential buyers [bold cyan]Conversion Rate:[/bold cyan] {(len(set(s.customer_id for s in self.all_sales))/len(self.setup.customers)*100):.1f}% Loading @@ -681,7 +681,7 @@ class VisualSimulation(SupplyChainSimulation): ) ) log_to_file( f"EXECUTIVE SUMMARY - Period: {self.config['simulation_days']} days, Revenue: ${total_revenue:,.0f}, Sales: {total_sales}, Customers: {len(self.setup.customers)}, Conversion: {(len(set(s.customer_id for s in self.all_sales))/len(self.setup.customers)*100):.1f}%" f"EXECUTIVE SUMMARY - Period: {self.config['simulation_days']} days, Revenue: CHF {total_revenue:,.0f}, Sales: {total_sales}, Customers: {len(self.setup.customers)}, Conversion: {(len(set(s.customer_id for s in self.all_sales))/len(self.setup.customers)*100):.1f}%" ) # Continue with the rest of the report... Loading Loading @@ -722,16 +722,16 @@ class VisualSimulation(SupplyChainSimulation): product_table.add_row( f"Watch_{watch.id}", watch.category.capitalize(), f"${watch.base_cost:.0f}", f"${watch.sell_price:.0f}", f"CHF {watch.base_cost:.0f}", f"CHF {watch.sell_price:.0f}", f"{perf['predicted']:.0f}", str(perf["sold"]), f"${perf['revenue']:,.0f}", f"CHF {perf['revenue']:,.0f}", f"{margin:.1f}%", ) log_to_file( f"Watch_{watch.id} ({watch.category}): Predicted={perf['predicted']:.0f}, Sold={perf['sold']}, Revenue=${perf['revenue']:,.0f}, Margin={margin:.1f}%" f"Watch_{watch.id} ({watch.category}): Predicted={perf['predicted']:.0f}, Sold={perf['sold']}, Revenue=CHF {perf['revenue']:,.0f}, Margin={margin:.1f}%" ) console.print(product_table) Loading @@ -755,7 +755,7 @@ class VisualSimulation(SupplyChainSimulation): operations_table.add_row( "Component Orders", str(total_orders), f"Total value: ${component_cost:,.0f}", f"Total value: CHF {component_cost:,.0f}", ) operations_table.add_row( "Average Lead Time", f"{avg_lead_time:.1f} days", "From order to delivery" Loading Loading @@ -845,8 +845,8 @@ class VisualSimulation(SupplyChainSimulation): str(n_customers), str(n_buyers), str(data["count"]), f"${data['revenue']:,.0f}", f"${avg_ticket:.0f}", f"CHF {data['revenue']:,.0f}", f"CHF {avg_ticket:.0f}", ) console.print(customer_table) Loading Loading @@ -905,14 +905,14 @@ class VisualSimulation(SupplyChainSimulation): financial_table.add_row("GMROI", f"{final_kpis['gmroi']:.3f}", gmroi_status) financial_table.add_row( "Total Revenue", f"${final_kpis['total_revenue']:,.0f}", "🟢" "Total Revenue", f"CHF {final_kpis['total_revenue']:,.0f}", "🟢" ) financial_table.add_row( "Gross Margin", f"${final_kpis['gross_margin']:,.0f}", "🟢" "Gross Margin", f"CHF {final_kpis['gross_margin']:,.0f}", "🟢" ) # financial_table.add_row("COGS", f"${final_kpis['cogs']:,.0f}", "—") # financial_table.add_row("COGS", f"CHF {final_kpis['cogs']:,.0f}", "—") financial_table.add_row( "Logistics Costs", f"${final_kpis['logistics_costs']:,.0f}", "—" "Logistics Costs", f"CHF {final_kpis['logistics_costs']:,.0f}", "—" ) margin_pct = ( Loading supply_chain_sim/customer_behavior.py +1 −1 Original line number Diff line number Diff line Loading @@ -150,7 +150,7 @@ class CustomerBehavior: if key in inv_map: inv_map[key].quantity -= 1 logger.debug(f"Customer {customer.id} purchased watch {watch_id} for ${retail_price:.0f}") logger.debug(f"Customer {customer.id} purchased watch {watch_id} for CHF {retail_price:.0f}") break # Customer only buys one watch per visit logger.info(f"Processed {len(new_sales)} sales") Loading supply_chain_sim/distribution.py +1 −1 Original line number Diff line number Diff line Loading @@ -136,7 +136,7 @@ class RetailDistribution: def calculate_distribution_costs(self) -> float: """Calculate total distribution/logistics costs.""" # Simplified cost model base_cost_per_unit = 5.0 # $5 per unit shipped base_cost_per_unit = 5.0 # CHF 5 per unit shipped total_cost = 0 for dist in self.distribution_history: Loading Loading
HorloML_EDU_Main/demo_prediction_phase.py +1 −1 Original line number Diff line number Diff line Loading @@ -43,7 +43,7 @@ def demo_basic_scenario(): print("\nProducts in simulation:") for watch in setup.watches: print(f" • Watch ID {watch.id}: {watch.name} ({watch.category})") print(f" Cost: ${watch.base_cost:.0f}, Price: ${watch.sell_price:.0f}") print(f" Cost: CHF {watch.base_cost:.0f}, Price: CHF {watch.sell_price:.0f}") # Student makes predictions for each product, each month print("\n[PREDICTION PHASE] Student enters predictions...") Loading
HorloML_EDU_Main/web_app/templates/predict.html +10 −10 Original line number Diff line number Diff line Loading @@ -68,8 +68,8 @@ {{ product.category|capitalize }} </span> </td> <td class="has-text-right">${{ "%.0f"|format(product.base_cost) }}</td> <td class="has-text-right">${{ "%.0f"|format(product.sell_price) }}</td> <td class="has-text-right">CHF {{ "%.0f"|format(product.base_cost) }}</td> <td class="has-text-right">CHF {{ "%.0f"|format(product.sell_price) }}</td> <td class="has-text-right">{{ "%.0f"|format(product.margin_pct) }}%</td> </tr> {% endfor %} Loading Loading @@ -192,35 +192,35 @@ <script> // Helper functions for quick-fill buttons function fillConstant(productId, nMonths) { const baseValue = document.getElementById(`pred_${productId}_1`).value || 10; const baseValue = document.getElementById(`pred_CHF {productId}_1`).value || 10; for (let month = 1; month <= nMonths; month++) { document.getElementById(`pred_${productId}_${month}`).value = baseValue; document.getElementById(`pred_CHF {productId}_CHF {month}`).value = baseValue; } } function fillIncreasing(productId, nMonths) { const baseValue = parseFloat(document.getElementById(`pred_${productId}_1`).value) || 10; const baseValue = parseFloat(document.getElementById(`pred_CHF {productId}_1`).value) || 10; const increment = 2; for (let month = 1; month <= nMonths; month++) { document.getElementById(`pred_${productId}_${month}`).value = Math.round(baseValue + (month - 1) * increment); document.getElementById(`pred_CHF {productId}_CHF {month}`).value = Math.round(baseValue + (month - 1) * increment); } } function fillDecreasing(productId, nMonths) { const baseValue = parseFloat(document.getElementById(`pred_${productId}_1`).value) || 20; const baseValue = parseFloat(document.getElementById(`pred_CHF {productId}_1`).value) || 20; const decrement = 1.5; for (let month = 1; month <= nMonths; month++) { const value = Math.max(1, Math.round(baseValue - (month - 1) * decrement)); document.getElementById(`pred_${productId}_${month}`).value = value; document.getElementById(`pred_CHF {productId}_CHF {month}`).value = value; } } function fillSeasonal(productId, nMonths) { const baseValue = parseFloat(document.getElementById(`pred_${productId}_1`).value) || 15; const baseValue = parseFloat(document.getElementById(`pred_CHF {productId}_1`).value) || 15; for (let month = 1; month <= nMonths; month++) { // Simple sine wave pattern const seasonal = baseValue + Math.sin(month * Math.PI / 6) * (baseValue * 0.3); document.getElementById(`pred_${productId}_${month}`).value = Math.round(Math.max(1, seasonal)); document.getElementById(`pred_CHF {productId}_CHF {month}`).value = Math.round(Math.max(1, seasonal)); } } Loading
supply_chain_sim/cmd_visual.py +24 −24 Original line number Diff line number Diff line Loading @@ -176,8 +176,8 @@ class VisualSimulation(SupplyChainSimulation): watch_table.add_row( f"Watch_{watch.id}", watch.category.capitalize(), f"${watch.base_cost:.0f}", f"${watch.sell_price:.0f}", f"CHF {watch.base_cost:.0f}", f"CHF {watch.sell_price:.0f}", f"{markup:.0f}%", ) Loading Loading @@ -329,7 +329,7 @@ class VisualSimulation(SupplyChainSimulation): f"Comp_{order.component_id}", str(current), str(order.quantity), f"${order.cost:.0f}", f"CHF {order.cost:.0f}", f"{order.expected_delivery:.1f}d", ) Loading @@ -344,7 +344,7 @@ class VisualSimulation(SupplyChainSimulation): ) ) log_to_file( f"Generated {len(supply_orders)} supply orders, total cost: ${sum(o.cost for o in supply_orders):.0f}" f"Generated {len(supply_orders)} supply orders, total cost: CHF {sum(o.cost for o in supply_orders):.0f}" ) phase_results.append(("Orders", len(supply_orders))) else: Loading Loading @@ -518,15 +518,15 @@ class VisualSimulation(SupplyChainSimulation): console.print( f" [green]✓ {len(new_sales)} sales from {len(sampled_customers)} visitors (conversion: {conversion:.1f}%)[/green]" ) console.print(f" [green]✓ Revenue: ${total_revenue:,.0f}[/green]") console.print(f" [green]✓ Revenue: CHF {total_revenue:,.0f}[/green]") log_to_file( f"{len(new_sales)} sales from {len(sampled_customers)} visitors (conversion: {conversion:.1f}%), Revenue: ${total_revenue:,.0f}" f"{len(new_sales)} sales from {len(sampled_customers)} visitors (conversion: {conversion:.1f}%), Revenue: CHF {total_revenue:,.0f}" ) sales_breakdown = [] for cat, data in sales_by_category.items(): sales_breakdown.append( f"{cat}: {data['count']} (${data['revenue']:,.0f})" f"{cat}: {data['count']} (CHF {data['revenue']:,.0f})" ) breakdown_str = ", ".join(sales_breakdown) console.print(f" [dim]Sales by category: {breakdown_str}[/dim]") Loading Loading @@ -609,13 +609,13 @@ class VisualSimulation(SupplyChainSimulation): "GMROI", f"{kpis['gmroi']:.2f}", "Total Revenue", f"${kpis['total_revenue']:,.0f}", f"CHF {kpis['total_revenue']:,.0f}", ) kpi_grid.add_row( "Gross Margin", f"${kpis['gross_margin']:,.0f}", f"CHF {kpis['gross_margin']:,.0f}", "Logistics Costs", f"${kpis['logistics_costs']:,.0f}", f"CHF {kpis['logistics_costs']:,.0f}", ) kpi_grid.add_row( "Warehouse Util", Loading @@ -626,7 +626,7 @@ class VisualSimulation(SupplyChainSimulation): console.print(Panel(kpi_grid, title="Daily KPIs", border_style="green")) log_to_file( f"Daily KPIs - GMROI: {kpis['gmroi']:.2f}, Revenue: ${kpis['total_revenue']:,.0f}, Margin: ${kpis['gross_margin']:,.0f}, Warehouse Util: {kpis['warehouse_capacity_utilization']:.1%}, Retailer Util: {kpis['retailer_capacity_utilization']:.1%}" f"Daily KPIs - GMROI: {kpis['gmroi']:.2f}, Revenue: CHF {kpis['total_revenue']:,.0f}, Margin: CHF {kpis['gross_margin']:,.0f}, Warehouse Util: {kpis['warehouse_capacity_utilization']:.1%}, Retailer Util: {kpis['retailer_capacity_utilization']:.1%}" ) # Store metrics for final report Loading Loading @@ -668,7 +668,7 @@ class VisualSimulation(SupplyChainSimulation): # Executive Summary exec_summary = f""" [bold cyan]Simulation Period:[/bold cyan] {self.config['simulation_days']} days [bold cyan]Total Revenue:[/bold cyan] ${total_revenue:,.0f} [bold cyan]Total Revenue:[/bold cyan] CHF {total_revenue:,.0f} [bold cyan]Total Sales:[/bold cyan] {total_sales} units [bold cyan]Customers:[/bold cyan] {len(self.setup.customers)} potential buyers [bold cyan]Conversion Rate:[/bold cyan] {(len(set(s.customer_id for s in self.all_sales))/len(self.setup.customers)*100):.1f}% Loading @@ -681,7 +681,7 @@ class VisualSimulation(SupplyChainSimulation): ) ) log_to_file( f"EXECUTIVE SUMMARY - Period: {self.config['simulation_days']} days, Revenue: ${total_revenue:,.0f}, Sales: {total_sales}, Customers: {len(self.setup.customers)}, Conversion: {(len(set(s.customer_id for s in self.all_sales))/len(self.setup.customers)*100):.1f}%" f"EXECUTIVE SUMMARY - Period: {self.config['simulation_days']} days, Revenue: CHF {total_revenue:,.0f}, Sales: {total_sales}, Customers: {len(self.setup.customers)}, Conversion: {(len(set(s.customer_id for s in self.all_sales))/len(self.setup.customers)*100):.1f}%" ) # Continue with the rest of the report... Loading Loading @@ -722,16 +722,16 @@ class VisualSimulation(SupplyChainSimulation): product_table.add_row( f"Watch_{watch.id}", watch.category.capitalize(), f"${watch.base_cost:.0f}", f"${watch.sell_price:.0f}", f"CHF {watch.base_cost:.0f}", f"CHF {watch.sell_price:.0f}", f"{perf['predicted']:.0f}", str(perf["sold"]), f"${perf['revenue']:,.0f}", f"CHF {perf['revenue']:,.0f}", f"{margin:.1f}%", ) log_to_file( f"Watch_{watch.id} ({watch.category}): Predicted={perf['predicted']:.0f}, Sold={perf['sold']}, Revenue=${perf['revenue']:,.0f}, Margin={margin:.1f}%" f"Watch_{watch.id} ({watch.category}): Predicted={perf['predicted']:.0f}, Sold={perf['sold']}, Revenue=CHF {perf['revenue']:,.0f}, Margin={margin:.1f}%" ) console.print(product_table) Loading @@ -755,7 +755,7 @@ class VisualSimulation(SupplyChainSimulation): operations_table.add_row( "Component Orders", str(total_orders), f"Total value: ${component_cost:,.0f}", f"Total value: CHF {component_cost:,.0f}", ) operations_table.add_row( "Average Lead Time", f"{avg_lead_time:.1f} days", "From order to delivery" Loading Loading @@ -845,8 +845,8 @@ class VisualSimulation(SupplyChainSimulation): str(n_customers), str(n_buyers), str(data["count"]), f"${data['revenue']:,.0f}", f"${avg_ticket:.0f}", f"CHF {data['revenue']:,.0f}", f"CHF {avg_ticket:.0f}", ) console.print(customer_table) Loading Loading @@ -905,14 +905,14 @@ class VisualSimulation(SupplyChainSimulation): financial_table.add_row("GMROI", f"{final_kpis['gmroi']:.3f}", gmroi_status) financial_table.add_row( "Total Revenue", f"${final_kpis['total_revenue']:,.0f}", "🟢" "Total Revenue", f"CHF {final_kpis['total_revenue']:,.0f}", "🟢" ) financial_table.add_row( "Gross Margin", f"${final_kpis['gross_margin']:,.0f}", "🟢" "Gross Margin", f"CHF {final_kpis['gross_margin']:,.0f}", "🟢" ) # financial_table.add_row("COGS", f"${final_kpis['cogs']:,.0f}", "—") # financial_table.add_row("COGS", f"CHF {final_kpis['cogs']:,.0f}", "—") financial_table.add_row( "Logistics Costs", f"${final_kpis['logistics_costs']:,.0f}", "—" "Logistics Costs", f"CHF {final_kpis['logistics_costs']:,.0f}", "—" ) margin_pct = ( Loading
supply_chain_sim/customer_behavior.py +1 −1 Original line number Diff line number Diff line Loading @@ -150,7 +150,7 @@ class CustomerBehavior: if key in inv_map: inv_map[key].quantity -= 1 logger.debug(f"Customer {customer.id} purchased watch {watch_id} for ${retail_price:.0f}") logger.debug(f"Customer {customer.id} purchased watch {watch_id} for CHF {retail_price:.0f}") break # Customer only buys one watch per visit logger.info(f"Processed {len(new_sales)} sales") Loading
supply_chain_sim/distribution.py +1 −1 Original line number Diff line number Diff line Loading @@ -136,7 +136,7 @@ class RetailDistribution: def calculate_distribution_costs(self) -> float: """Calculate total distribution/logistics costs.""" # Simplified cost model base_cost_per_unit = 5.0 # $5 per unit shipped base_cost_per_unit = 5.0 # CHF 5 per unit shipped total_cost = 0 for dist in self.distribution_history: Loading