feat: Complete backend i18n implementation with error codes and demo data
Demo Seed Scripts: - Updated seed_demo_purchase_orders.py to use structured reasoning_data * Imports create_po_reasoning_low_stock and create_po_reasoning_supplier_contract * Generates reasoning_data with product names, stock levels, and consequences * Removed deprecated reasoning/consequence TEXT fields - Updated seed_demo_batches.py to use structured reasoning_data * Imports create_batch_reasoning_forecast_demand and create_batch_reasoning_regular_schedule * Generates intelligent reasoning based on batch priority and AI assistance * Adds reasoning_data to all production batches Backend Services - Error Code Implementation: - Updated safety_stock_calculator.py with error codes * Replaced "Lead time or demand std dev is zero or negative" with ERROR:LEAD_TIME_INVALID * Replaced "Insufficient historical demand data" with ERROR:INSUFFICIENT_DATA - Updated replenishment_planning_service.py with error codes * Replaced "Insufficient data for safety stock calculation" with ERROR:INSUFFICIENT_DATA * Frontend can now translate error codes using i18n Demo data will now display with translatable reasoning in EN/ES/EU languages. Backend services return error codes that frontend translates for user's language.
This commit is contained in:
@@ -373,7 +373,7 @@ class ReplenishmentPlanningService:
|
||||
lead_time_days=req.lead_time_days,
|
||||
calculation_method='none',
|
||||
confidence='low',
|
||||
reasoning='Insufficient data for safety stock calculation'
|
||||
reasoning='ERROR:INSUFFICIENT_DATA' # Error code for i18n translation
|
||||
)
|
||||
|
||||
def _adjust_for_shelf_life(
|
||||
|
||||
@@ -108,7 +108,7 @@ class SafetyStockCalculator:
|
||||
lead_time_days=lead_time_days,
|
||||
calculation_method='zero_due_to_invalid_inputs',
|
||||
confidence='low',
|
||||
reasoning='Lead time or demand std dev is zero or negative'
|
||||
reasoning='ERROR:LEAD_TIME_INVALID' # Error code for i18n translation
|
||||
)
|
||||
|
||||
# Safety Stock = Z × σ × √L
|
||||
@@ -160,7 +160,7 @@ class SafetyStockCalculator:
|
||||
lead_time_days=lead_time_days,
|
||||
calculation_method='insufficient_data',
|
||||
confidence='low',
|
||||
reasoning='Insufficient historical demand data (need at least 2 data points)'
|
||||
reasoning='ERROR:INSUFFICIENT_DATA' # Error code for i18n translation
|
||||
)
|
||||
|
||||
# Calculate standard deviation
|
||||
|
||||
@@ -34,6 +34,13 @@ from app.models.purchase_order import (
|
||||
PurchaseOrder, PurchaseOrderItem, PurchaseOrderStatus
|
||||
)
|
||||
|
||||
# Import reasoning helper functions for i18n support
|
||||
sys.path.insert(0, str(Path(__file__).parent.parent.parent.parent))
|
||||
from shared.schemas.reasoning_types import (
|
||||
create_po_reasoning_low_stock,
|
||||
create_po_reasoning_supplier_contract
|
||||
)
|
||||
|
||||
# Configure logging
|
||||
logger = structlog.get_logger()
|
||||
|
||||
@@ -116,31 +123,39 @@ async def create_purchase_order(
|
||||
|
||||
# Generate reasoning for JTBD dashboard (if columns exist after migration)
|
||||
days_until_delivery = (required_delivery - created_at).days
|
||||
reasoning_text = None
|
||||
reasoning_json = None
|
||||
consequence_text = None
|
||||
|
||||
# Generate structured reasoning_data for i18n support
|
||||
reasoning_data = None
|
||||
|
||||
try:
|
||||
# Try to set reasoning fields (will work after migration)
|
||||
# Get product names from items
|
||||
product_names = [item.product_name for item in items if hasattr(item, 'product_name')]
|
||||
if not product_names:
|
||||
product_names = [f"Product {i+1}" for i in range(len(items))]
|
||||
|
||||
if status == PurchaseOrderStatus.pending_approval:
|
||||
reasoning_text = f"Low stock detected for {supplier.name} items. Current inventory projected to run out in {days_until_delivery + 2} days."
|
||||
consequence_text = f"Stock-out risk in {days_until_delivery + 2} days if not approved. Production may be impacted."
|
||||
reasoning_json = {
|
||||
"trigger": "low_stock",
|
||||
"urgency_score": 75 if days_until_delivery < 5 else 50,
|
||||
"days_remaining": days_until_delivery + 2,
|
||||
"supplier_trust_score": supplier.trust_score
|
||||
}
|
||||
# Low stock detection reasoning
|
||||
days_until_stockout = days_until_delivery + 2
|
||||
reasoning_data = create_po_reasoning_low_stock(
|
||||
supplier_name=supplier.name,
|
||||
product_names=product_names,
|
||||
current_stock=random.uniform(20, 50), # Demo: low stock
|
||||
required_stock=random.uniform(100, 200), # Demo: needed stock
|
||||
days_until_stockout=days_until_stockout,
|
||||
threshold_percentage=20,
|
||||
affected_products=product_names[:2] if len(product_names) > 1 else product_names,
|
||||
estimated_lost_orders=random.randint(5, 15) if days_until_stockout <= 3 else None
|
||||
)
|
||||
elif auto_approved:
|
||||
reasoning_text = f"Auto-approved based on supplier trust score ({supplier.trust_score:.0%}) and amount within threshold (€{subtotal:.2f})."
|
||||
reasoning_json = {
|
||||
"trigger": "auto_approval",
|
||||
"trust_score": supplier.trust_score,
|
||||
"amount": float(subtotal),
|
||||
"threshold": 500.0
|
||||
}
|
||||
except Exception:
|
||||
# Columns don't exist yet, that's ok
|
||||
# Supplier contract/auto-approval reasoning
|
||||
reasoning_data = create_po_reasoning_supplier_contract(
|
||||
supplier_name=supplier.name,
|
||||
product_names=product_names,
|
||||
contract_terms=f"Auto-approval threshold: €500",
|
||||
trust_score=float(supplier.trust_score) if hasattr(supplier, 'trust_score') else 0.85
|
||||
)
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to generate reasoning_data: {e}")
|
||||
pass
|
||||
|
||||
# Create PO
|
||||
@@ -165,12 +180,10 @@ async def create_purchase_order(
|
||||
updated_by=SYSTEM_USER_ID
|
||||
)
|
||||
|
||||
# Set reasoning fields if they exist (after migration)
|
||||
if reasoning_text:
|
||||
# Set structured reasoning_data for i18n support
|
||||
if reasoning_data:
|
||||
try:
|
||||
po.reasoning = reasoning_text
|
||||
po.consequence = consequence_text
|
||||
po.reasoning_data = reasoning_json
|
||||
po.reasoning_data = reasoning_data
|
||||
except Exception:
|
||||
pass # Columns don't exist yet
|
||||
|
||||
|
||||
@@ -25,6 +25,10 @@ import structlog
|
||||
|
||||
from app.models.production import ProductionBatch, ProductionStatus, ProductionPriority, ProcessStage
|
||||
|
||||
# Import reasoning helper functions for i18n support
|
||||
sys.path.insert(0, str(Path(__file__).parent.parent.parent.parent))
|
||||
from shared.schemas.reasoning_types import create_batch_reasoning_forecast_demand, create_batch_reasoning_regular_schedule
|
||||
|
||||
# Configure logging
|
||||
logger = structlog.get_logger()
|
||||
|
||||
@@ -161,6 +165,29 @@ async def seed_batches_for_tenant(
|
||||
# For La Espiga, append tenant suffix to make batch number unique
|
||||
batch_number = batch_data["batch_number"] + "-LE"
|
||||
|
||||
# Generate structured reasoning_data for i18n support
|
||||
reasoning_data = None
|
||||
try:
|
||||
# Use forecast demand reasoning for most batches
|
||||
if batch_data.get("is_ai_assisted") or priority in [ProductionPriority.HIGH, ProductionPriority.URGENT]:
|
||||
reasoning_data = create_batch_reasoning_forecast_demand(
|
||||
product_name=batch_data["product_name"],
|
||||
predicted_demand=batch_data["planned_quantity"],
|
||||
current_stock=int(batch_data["planned_quantity"] * 0.3), # Demo: assume 30% current stock
|
||||
production_needed=batch_data["planned_quantity"],
|
||||
target_date=planned_start.date().isoformat(),
|
||||
confidence_score=0.85 if batch_data.get("is_ai_assisted") else 0.75
|
||||
)
|
||||
else:
|
||||
# Regular schedule reasoning for standard batches
|
||||
reasoning_data = create_batch_reasoning_regular_schedule(
|
||||
product_name=batch_data["product_name"],
|
||||
schedule_frequency="daily",
|
||||
batch_size=batch_data["planned_quantity"]
|
||||
)
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to generate reasoning_data for batch {batch_number}: {e}")
|
||||
|
||||
# Create production batch
|
||||
batch = ProductionBatch(
|
||||
id=batch_id,
|
||||
@@ -197,6 +224,7 @@ async def seed_batches_for_tenant(
|
||||
waste_defect_type=batch_data.get("waste_defect_type"),
|
||||
production_notes=batch_data.get("production_notes"),
|
||||
quality_notes=batch_data.get("quality_notes"),
|
||||
reasoning_data=reasoning_data, # Structured reasoning for i18n support
|
||||
created_at=BASE_REFERENCE_DATE,
|
||||
updated_at=BASE_REFERENCE_DATE,
|
||||
completed_at=completed_at
|
||||
|
||||
Reference in New Issue
Block a user