From be8cb20b187f0e6b9fa531249d4d2b81135bf766 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 7 Nov 2025 19:00:00 +0000 Subject: [PATCH] fix: Replace all remaining hardcoded English reasoning with structured codes This commit removes the last hardcoded English text from reasoning fields across all backend services, completing the i18n implementation. Changes by service: Safety Stock Calculator (safety_stock_calculator.py): - CALC:STATISTICAL_Z_SCORE - Statistical calculation with Z-score - CALC:ADVANCED_VARIABILITY - Advanced formula with demand and lead time variability - CALC:FIXED_PERCENTAGE - Fixed percentage of lead time demand - All calculation methods now use structured codes with pipe-separated parameters Price Forecaster (price_forecaster.py): - PRICE_FORECAST:DECREASE_EXPECTED - Price expected to decrease - PRICE_FORECAST:INCREASE_EXPECTED - Price expected to increase - PRICE_FORECAST:HIGH_VOLATILITY - High price volatility detected - PRICE_FORECAST:BELOW_AVERAGE - Current price below average (buy opportunity) - PRICE_FORECAST:STABLE - Price stable, normal schedule - All forecasts include relevant parameters (change_pct, days, etc.) Optimization Utils (shared/utils/optimization.py): - EOQ:BASE - Economic Order Quantity base calculation - EOQ:MOQ_APPLIED - Minimum order quantity constraint applied - EOQ:MAX_APPLIED - Maximum order quantity constraint applied - TIER_PRICING:CURRENT_TIER - Current tier pricing - TIER_PRICING:UPGRADED - Upgraded to higher tier for savings - All optimizations include calculation parameters Format: All codes use pattern "CATEGORY:TYPE|param1=value|param2=value" This allows frontend to parse and translate with parameters while maintaining technical accuracy for logging and debugging. Frontend can now translate ALL reasoning codes across the entire system. --- services/procurement/app/ml/price_forecaster.py | 13 +++++++------ .../app/services/safety_stock_calculator.py | 15 ++++++--------- shared/utils/optimization.py | 13 ++++++++----- 3 files changed, 21 insertions(+), 20 deletions(-) diff --git a/services/procurement/app/ml/price_forecaster.py b/services/procurement/app/ml/price_forecaster.py index 5df56b54..755b0a47 100644 --- a/services/procurement/app/ml/price_forecaster.py +++ b/services/procurement/app/ml/price_forecaster.py @@ -498,35 +498,36 @@ class PriceForecaster: # Calculate expected price change expected_change_pct = ((forecast_mean - current_price) / current_price * 100) if current_price > 0 else 0 - # Decision logic + # Decision logic with i18n-friendly reasoning codes if expected_change_pct < -5: # Price expected to drop >5% action = 'wait' - reasoning = f'Price expected to decrease by {abs(expected_change_pct):.1f}% in next 30 days. Delay purchase.' + reasoning = f'PRICE_FORECAST:DECREASE_EXPECTED|change_pct={abs(expected_change_pct):.1f}|days=30' urgency = 'low' elif expected_change_pct > 5: # Price expected to increase >5% action = 'buy_now' - reasoning = f'Price expected to increase by {expected_change_pct:.1f}% in next 30 days. Purchase soon.' + reasoning = f'PRICE_FORECAST:INCREASE_EXPECTED|change_pct={expected_change_pct:.1f}|days=30' urgency = 'high' elif volatility['volatility_level'] == 'high': # High volatility - wait for dip action = 'wait_for_dip' - reasoning = f'High price volatility (CV={volatility["coefficient_of_variation"]:.2f}). Wait for favorable dip.' + reasoning = f'PRICE_FORECAST:HIGH_VOLATILITY|coefficient={volatility["coefficient_of_variation"]:.2f}' urgency = 'medium' elif current_price < price_stats['mean_price'] * 0.95: # Currently below average + below_avg_pct = ((price_stats["mean_price"] - current_price) / price_stats["mean_price"] * 100) action = 'buy_now' - reasoning = f'Current price €{current_price:.2f} is {((price_stats["mean_price"] - current_price) / price_stats["mean_price"] * 100):.1f}% below average. Good buying opportunity.' + reasoning = f'PRICE_FORECAST:BELOW_AVERAGE|current_price={current_price:.2f}|below_avg_pct={below_avg_pct:.1f}' urgency = 'medium' else: # Neutral action = 'normal_purchase' - reasoning = 'Price stable. Follow normal procurement schedule.' + reasoning = 'PRICE_FORECAST:STABLE' urgency = 'low' # Optimal purchase timing diff --git a/services/procurement/app/services/safety_stock_calculator.py b/services/procurement/app/services/safety_stock_calculator.py index ba88087b..a48a1a47 100644 --- a/services/procurement/app/services/safety_stock_calculator.py +++ b/services/procurement/app/services/safety_stock_calculator.py @@ -117,10 +117,8 @@ class SafetyStockCalculator: # Determine confidence confidence = self._determine_confidence(demand_std_dev, lead_time_days) - reasoning = ( - f"Service level {service_level*100:.1f}% (Z={z_score:.2f}) × " - f"Demand σ={demand_std_dev:.2f} × √{lead_time_days} days" - ) + # Use calculation method as reasoning code with parameters + reasoning = f"CALC:STATISTICAL_Z_SCORE|service_level={service_level*100:.1f}|z_score={z_score:.2f}|demand_std={demand_std_dev:.2f}|lead_time={lead_time_days}" return SafetyStockResult( safety_stock_quantity=Decimal(str(round(safety_stock, 2))), @@ -211,10 +209,8 @@ class SafetyStockCalculator: confidence = 'high' if lead_time_std_dev > 0 else 'medium' - reasoning = ( - f"Advanced formula considering both demand variability " - f"(σ={demand_std_dev:.2f}) and lead time variability (σ={lead_time_std_dev:.1f} days)" - ) + # Use calculation method as reasoning code with parameters + reasoning = f"CALC:ADVANCED_VARIABILITY|demand_std={demand_std_dev:.2f}|lead_time_std={lead_time_std_dev:.1f}" return SafetyStockResult( safety_stock_quantity=Decimal(str(round(safety_stock, 2))), @@ -249,7 +245,8 @@ class SafetyStockCalculator: lead_time_demand = average_demand * lead_time_days safety_stock = lead_time_demand * percentage - reasoning = f"{percentage*100:.0f}% of lead time demand ({lead_time_demand:.2f})" + # Use calculation method as reasoning code with parameters + reasoning = f"CALC:FIXED_PERCENTAGE|percentage={percentage*100:.0f}|lead_time_demand={lead_time_demand:.2f}" return SafetyStockResult( safety_stock_quantity=Decimal(str(round(safety_stock, 2))), diff --git a/shared/utils/optimization.py b/shared/utils/optimization.py index d525b758..8ec3bd59 100644 --- a/shared/utils/optimization.py +++ b/shared/utils/optimization.py @@ -87,17 +87,20 @@ def optimize_order_quantity( # Start with EOQ or required quantity, whichever is larger optimal_qty = max(float(required_quantity), eoq) - reasoning = f"Base EOQ: {eoq:.2f}, Required: {required_quantity}" + # Build structured reasoning code with parameters + reasoning_parts = [f"EOQ:BASE|eoq={eoq:.2f}|required={required_quantity}"] # Apply minimum order quantity if min_order_qty and Decimal(optimal_qty) < min_order_qty: optimal_qty = float(min_order_qty) - reasoning += f", Applied MOQ: {min_order_qty}" + reasoning_parts.append(f"EOQ:MOQ_APPLIED|moq={min_order_qty}") # Apply maximum order quantity if max_order_qty and Decimal(optimal_qty) > max_order_qty: optimal_qty = float(max_order_qty) - reasoning += f", Capped at max: {max_order_qty}" + reasoning_parts.append(f"EOQ:MAX_APPLIED|max={max_order_qty}") + + reasoning = "|".join(reasoning_parts) # Calculate costs orders_per_year = annual_demand / optimal_qty if optimal_qty > 0 else 0 @@ -208,7 +211,7 @@ def apply_price_tier_optimization( best_quantity = base_quantity best_price = current_tier_price best_savings = Decimal('0') - reasoning = f"Current tier price: ${current_tier_price}" + reasoning = f"TIER_PRICING:CURRENT_TIER|price={current_tier_price}" for tier in sorted_tiers: tier_min_qty = Decimal(str(tier['min_quantity'])) @@ -232,7 +235,7 @@ def apply_price_tier_optimization( best_quantity = tier_min_qty best_price = tier_price best_savings = savings - reasoning = f"Upgraded to tier {tier_min_qty}+ for ${savings:.2f} savings" + reasoning = f"TIER_PRICING:UPGRADED|tier_min={tier_min_qty}|savings={savings:.2f}" return best_quantity, best_price, reasoning