Improve te panel de control logic
This commit is contained in:
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user