refactor: Convert internal services to structured JSON reasoning
Convert pipe-separated reasoning codes to structured JSON format for: - Safety stock calculator (statistical calculations, errors) - Price forecaster (procurement recommendations, volatility) - Order optimization (EOQ, tier pricing) This enables i18n translation of internal calculation reasoning and provides structured data for frontend AI insights display. Benefits: - Consistent with PO/Batch reasoning_data format - Frontend can translate using same i18n infrastructure - Structured parameters enable rich UI visualization - No legacy string parsing needed Changes: - safety_stock_calculator.py: Replace reasoning str with reasoning_data dict - price_forecaster.py: Convert recommendation reasoning to structured format - optimization.py: Update EOQ and tier pricing to use reasoning_data Part of complete i18n implementation for AI insights.
This commit is contained in:
@@ -502,32 +502,69 @@ class PriceForecaster:
|
||||
if expected_change_pct < -5:
|
||||
# Price expected to drop >5%
|
||||
action = 'wait'
|
||||
reasoning = f'PRICE_FORECAST:DECREASE_EXPECTED|change_pct={abs(expected_change_pct):.1f}|days=30'
|
||||
reasoning_data = {
|
||||
'type': 'decrease_expected',
|
||||
'parameters': {
|
||||
'change_pct': round(abs(expected_change_pct), 1),
|
||||
'forecast_days': 30,
|
||||
'current_price': round(current_price, 2),
|
||||
'forecast_mean': round(forecast_mean, 2)
|
||||
}
|
||||
}
|
||||
urgency = 'low'
|
||||
|
||||
elif expected_change_pct > 5:
|
||||
# Price expected to increase >5%
|
||||
action = 'buy_now'
|
||||
reasoning = f'PRICE_FORECAST:INCREASE_EXPECTED|change_pct={expected_change_pct:.1f}|days=30'
|
||||
reasoning_data = {
|
||||
'type': 'increase_expected',
|
||||
'parameters': {
|
||||
'change_pct': round(expected_change_pct, 1),
|
||||
'forecast_days': 30,
|
||||
'current_price': round(current_price, 2),
|
||||
'forecast_mean': round(forecast_mean, 2)
|
||||
}
|
||||
}
|
||||
urgency = 'high'
|
||||
|
||||
elif volatility['volatility_level'] == 'high':
|
||||
# High volatility - wait for dip
|
||||
action = 'wait_for_dip'
|
||||
reasoning = f'PRICE_FORECAST:HIGH_VOLATILITY|coefficient={volatility["coefficient_of_variation"]:.2f}'
|
||||
reasoning_data = {
|
||||
'type': 'high_volatility',
|
||||
'parameters': {
|
||||
'coefficient': round(volatility['coefficient_of_variation'], 2),
|
||||
'volatility_level': volatility['volatility_level'],
|
||||
'avg_daily_change_pct': round(volatility['avg_daily_change_pct'], 2)
|
||||
}
|
||||
}
|
||||
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'PRICE_FORECAST:BELOW_AVERAGE|current_price={current_price:.2f}|below_avg_pct={below_avg_pct:.1f}'
|
||||
reasoning_data = {
|
||||
'type': 'below_average',
|
||||
'parameters': {
|
||||
'current_price': round(current_price, 2),
|
||||
'mean_price': round(price_stats['mean_price'], 2),
|
||||
'below_avg_pct': round(below_avg_pct, 1)
|
||||
}
|
||||
}
|
||||
urgency = 'medium'
|
||||
|
||||
else:
|
||||
# Neutral
|
||||
action = 'normal_purchase'
|
||||
reasoning = 'PRICE_FORECAST:STABLE'
|
||||
reasoning_data = {
|
||||
'type': 'stable',
|
||||
'parameters': {
|
||||
'current_price': round(current_price, 2),
|
||||
'forecast_mean': round(forecast_mean, 2),
|
||||
'expected_change_pct': round(expected_change_pct, 2)
|
||||
}
|
||||
}
|
||||
urgency = 'low'
|
||||
|
||||
# Optimal purchase timing
|
||||
@@ -536,7 +573,7 @@ class PriceForecaster:
|
||||
|
||||
return {
|
||||
'action': action,
|
||||
'reasoning': reasoning,
|
||||
'reasoning_data': reasoning_data,
|
||||
'urgency': urgency,
|
||||
'expected_price_change_pct': round(expected_change_pct, 2),
|
||||
'current_price': current_price,
|
||||
@@ -617,7 +654,7 @@ class PriceForecaster:
|
||||
'priority': recommendations['urgency'],
|
||||
'category': 'procurement',
|
||||
'title': f'Buy Now: Price Increasing {recommendations["expected_price_change_pct"]:.1f}%',
|
||||
'description': recommendations['reasoning'],
|
||||
'reasoning_data': recommendations['reasoning_data'],
|
||||
'impact_type': 'cost_avoidance',
|
||||
'impact_value': abs(recommendations['expected_price_change_pct']),
|
||||
'impact_unit': 'percentage',
|
||||
@@ -651,7 +688,11 @@ class PriceForecaster:
|
||||
'priority': 'medium',
|
||||
'category': 'procurement',
|
||||
'title': f'Wait to Buy: Price Decreasing {abs(recommendations["expected_price_change_pct"]):.1f}%',
|
||||
'description': recommendations['reasoning'] + f' Optimal purchase date: {recommendations["optimal_purchase_date"]}.',
|
||||
'reasoning_data': {
|
||||
**recommendations['reasoning_data'],
|
||||
'optimal_purchase_date': recommendations['optimal_purchase_date'],
|
||||
'days_until_optimal': recommendations['days_until_optimal']
|
||||
},
|
||||
'impact_type': 'cost_savings',
|
||||
'impact_value': abs(recommendations['expected_price_change_pct']),
|
||||
'impact_unit': 'percentage',
|
||||
@@ -788,7 +829,13 @@ class PriceForecaster:
|
||||
'volatility': {},
|
||||
'recommendations': {
|
||||
'action': 'insufficient_data',
|
||||
'reasoning': 'Not enough price history for reliable forecast. Need at least 180 days.',
|
||||
'reasoning_data': {
|
||||
'type': 'insufficient_data',
|
||||
'parameters': {
|
||||
'history_days': len(price_history),
|
||||
'min_required_days': 180
|
||||
}
|
||||
},
|
||||
'urgency': 'low'
|
||||
},
|
||||
'bulk_opportunities': {'has_bulk_opportunity': False},
|
||||
|
||||
Reference in New Issue
Block a user