From ed7db4d4f2424e6c0724c2db28423fe59878ba7f Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 7 Nov 2025 18:40:44 +0000 Subject: [PATCH] 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. --- .../replenishment_planning_service.py | 2 +- .../app/services/safety_stock_calculator.py | 4 +- .../scripts/demo/seed_demo_purchase_orders.py | 65 +++++++++++-------- .../scripts/demo/seed_demo_batches.py | 28 ++++++++ 4 files changed, 70 insertions(+), 29 deletions(-) diff --git a/services/procurement/app/services/replenishment_planning_service.py b/services/procurement/app/services/replenishment_planning_service.py index 985eb1f9..a894e596 100644 --- a/services/procurement/app/services/replenishment_planning_service.py +++ b/services/procurement/app/services/replenishment_planning_service.py @@ -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( diff --git a/services/procurement/app/services/safety_stock_calculator.py b/services/procurement/app/services/safety_stock_calculator.py index 60b059b4..ba88087b 100644 --- a/services/procurement/app/services/safety_stock_calculator.py +++ b/services/procurement/app/services/safety_stock_calculator.py @@ -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 diff --git a/services/procurement/scripts/demo/seed_demo_purchase_orders.py b/services/procurement/scripts/demo/seed_demo_purchase_orders.py index 46ca5334..27b49c29 100644 --- a/services/procurement/scripts/demo/seed_demo_purchase_orders.py +++ b/services/procurement/scripts/demo/seed_demo_purchase_orders.py @@ -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 diff --git a/services/production/scripts/demo/seed_demo_batches.py b/services/production/scripts/demo/seed_demo_batches.py index 8d81bdbd..4b427808 100755 --- a/services/production/scripts/demo/seed_demo_batches.py +++ b/services/production/scripts/demo/seed_demo_batches.py @@ -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