Improve te panel de control logic

This commit is contained in:
Urtzi Alfaro
2025-11-21 16:15:09 +01:00
parent 2ee94fb4b1
commit 3242c8d837
21 changed files with 2805 additions and 696 deletions

View File

@@ -125,33 +125,118 @@ class ProductionBatchReasoningData(BaseModel):
def create_po_reasoning_low_stock(
supplier_name: str,
product_names: list,
current_stock: float,
required_stock: float,
days_until_stockout: int,
product_names: list, # Kept for backward compatibility
current_stock: float = None, # Kept for backward compatibility
required_stock: float = None, # Kept for backward compatibility
days_until_stockout: int = None, # Kept for backward compatibility
threshold_percentage: int = 20,
affected_products: Optional[list] = None,
estimated_lost_orders: Optional[int] = None
estimated_lost_orders: Optional[int] = None,
# New enhanced parameters
product_details: Optional[list] = None,
supplier_lead_time_days: Optional[int] = None,
order_urgency: Optional[str] = None,
affected_production_batches: Optional[list] = None,
estimated_production_loss_eur: Optional[float] = None
) -> Dict[str, Any]:
"""
Create reasoning data for low stock detection
Create reasoning data for low stock detection with supply chain intelligence
Supports two modes:
1. Legacy mode: Uses product_names, current_stock, days_until_stockout (simple)
2. Enhanced mode: Uses product_details with per-product depletion analysis
Args:
supplier_name: Name of the supplier
product_names: List of product names in the order
current_stock: Current stock level
required_stock: Required stock level
days_until_stockout: Days until stock runs out
product_names: List of product names (legacy, for backward compatibility)
current_stock: Current stock level (legacy)
required_stock: Required stock level (legacy)
days_until_stockout: Days until stock runs out (legacy)
threshold_percentage: Stock threshold percentage
affected_products: Products that will be affected
estimated_lost_orders: Estimated number of lost orders
affected_products: Products that will be affected (legacy)
estimated_lost_orders: Estimated number of lost orders (legacy)
product_details: List of dicts with per-product analysis (NEW):
[
{
"product_name": str,
"current_stock_kg": float,
"daily_consumption_kg": float,
"days_until_depletion": float,
"reorder_point_kg": float,
"safety_stock_days": int,
"criticality": str # "critical", "urgent", "important", "normal"
},
...
]
supplier_lead_time_days: Supplier delivery lead time in days (NEW)
order_urgency: Overall order urgency: "critical", "urgent", "important", "normal" (NEW)
affected_production_batches: List of batch numbers that will be impacted (NEW)
estimated_production_loss_eur: Estimated financial loss if not ordered (NEW)
Returns:
Reasoning data dictionary
Reasoning data dictionary with enhanced supply chain intelligence
"""
return {
"type": PurchaseOrderReasoningType.LOW_STOCK_DETECTION.value,
"parameters": {
# Determine mode based on parameters
enhanced_mode = product_details is not None
if enhanced_mode:
# Enhanced mode: Use detailed per-product analysis
# Extract critical products for summary
critical_products = [
p for p in product_details
if p.get("criticality") in ["critical", "urgent"]
]
# Find most critical depletion time
min_depletion_days = min(
[p.get("days_until_depletion", 999) for p in product_details],
default=7
)
min_depletion_hours = round(min_depletion_days * 24, 1)
# Calculate safety margin with supplier lead time
safety_margin_days = None
if supplier_lead_time_days is not None:
safety_margin_days = min_depletion_days - supplier_lead_time_days
# All product names for backward compatibility
all_product_names = [p.get("product_name", "Product") for p in product_details]
parameters = {
"supplier_name": supplier_name,
"supplier_lead_time_days": supplier_lead_time_days or 2,
"product_details": product_details,
"product_names": all_product_names, # For i18n join operations
"product_count": len(product_details),
"critical_products": [p.get("product_name") for p in critical_products],
"critical_product_count": len(critical_products),
"min_depletion_days": round(min_depletion_days, 2),
"min_depletion_hours": min_depletion_hours,
"safety_margin_days": round(safety_margin_days, 2) if safety_margin_days is not None else None,
"order_urgency": order_urgency or "normal",
"affected_batches": affected_production_batches or [],
"affected_batches_count": len(affected_production_batches) if affected_production_batches else 0,
"potential_loss_eur": round(estimated_production_loss_eur, 2) if estimated_production_loss_eur else 0,
"threshold_percentage": threshold_percentage
}
# Enhanced consequence calculation
severity = ConsequenceSeverity.CRITICAL.value if order_urgency == "critical" else \
ConsequenceSeverity.HIGH.value if order_urgency in ["urgent", "important"] else \
ConsequenceSeverity.MEDIUM.value
consequence = {
"type": "stockout_risk_detailed",
"severity": severity,
"impact_days": round(min_depletion_days, 2),
"affected_production_batches": affected_production_batches or [],
"estimated_production_loss_eur": round(estimated_production_loss_eur, 2) if estimated_production_loss_eur else 0,
"critical_items": [p.get("product_name") for p in critical_products]
}
else:
# Legacy mode: Use simple parameters (backward compatibility)
parameters = {
"supplier_name": supplier_name,
"product_names": product_names,
"product_count": len(product_names),
@@ -159,18 +244,25 @@ def create_po_reasoning_low_stock(
"required_stock": required_stock,
"days_until_stockout": days_until_stockout,
"threshold_percentage": threshold_percentage,
"stock_percentage": round((current_stock / required_stock * 100), 1) if required_stock > 0 else 0
},
"consequence": {
"stock_percentage": round((current_stock / required_stock * 100), 1) if (required_stock and required_stock > 0) else 0
}
consequence = {
"type": "stockout_risk",
"severity": ConsequenceSeverity.HIGH.value if days_until_stockout <= 2 else ConsequenceSeverity.MEDIUM.value,
"impact_days": days_until_stockout,
"severity": ConsequenceSeverity.HIGH.value if days_until_stockout and days_until_stockout <= 2 else ConsequenceSeverity.MEDIUM.value,
"impact_days": days_until_stockout or 7,
"affected_products": affected_products or [],
"estimated_lost_orders": estimated_lost_orders or 0
},
}
return {
"type": PurchaseOrderReasoningType.LOW_STOCK_DETECTION.value,
"parameters": parameters,
"consequence": consequence,
"metadata": {
"trigger_source": "orchestrator_auto",
"ai_assisted": True
"ai_assisted": True,
"enhanced_mode": enhanced_mode
}
}