Improve AI logic
This commit is contained in:
@@ -24,8 +24,8 @@ logger = structlog.get_logger()
|
||||
class ProcurementServiceClient(BaseServiceClient):
|
||||
"""Enhanced client for communicating with the Procurement Service"""
|
||||
|
||||
def __init__(self, config: BaseServiceSettings):
|
||||
super().__init__("procurement", config)
|
||||
def __init__(self, config: BaseServiceSettings, calling_service_name: str = "unknown"):
|
||||
super().__init__(calling_service_name, config)
|
||||
|
||||
def get_service_base_path(self) -> str:
|
||||
return "/api/v1"
|
||||
@@ -63,7 +63,7 @@ class ProcurementServiceClient(BaseServiceClient):
|
||||
recipes_data: Optional recipes snapshot (NEW - to avoid duplicate fetching)
|
||||
"""
|
||||
try:
|
||||
path = f"/tenants/{tenant_id}/procurement/auto-generate"
|
||||
path = f"/tenants/{tenant_id}/procurement/operations/auto-generate"
|
||||
payload = {
|
||||
"forecast_data": forecast_data,
|
||||
"production_schedule_id": production_schedule_id,
|
||||
@@ -84,7 +84,9 @@ class ProcurementServiceClient(BaseServiceClient):
|
||||
tenant_id=tenant_id,
|
||||
has_forecast_data=bool(forecast_data))
|
||||
|
||||
response = await self._post(path, json=payload)
|
||||
# Remove tenant_id from path since it's passed as separate parameter
|
||||
endpoint = f"procurement/operations/auto-generate"
|
||||
response = await self.post(endpoint, data=payload, tenant_id=tenant_id)
|
||||
return response
|
||||
|
||||
except Exception as e:
|
||||
@@ -127,7 +129,7 @@ class ProcurementServiceClient(BaseServiceClient):
|
||||
- items: List of plan items with full metadata
|
||||
"""
|
||||
try:
|
||||
path = f"/tenants/{tenant_id}/replenishment-plans/generate"
|
||||
path = f"/tenants/{tenant_id}/procurement/operations/replenishment-plans/generate"
|
||||
payload = {
|
||||
"tenant_id": tenant_id,
|
||||
"requirements": requirements,
|
||||
@@ -142,7 +144,9 @@ class ProcurementServiceClient(BaseServiceClient):
|
||||
tenant_id=tenant_id,
|
||||
requirements_count=len(requirements))
|
||||
|
||||
response = await self._post(path, json=payload)
|
||||
# Remove tenant_id from path since it's passed as separate parameter
|
||||
endpoint = f"procurement/operations/replenishment-plans/generate"
|
||||
response = await self.post(endpoint, data=payload, tenant_id=tenant_id)
|
||||
return response
|
||||
|
||||
except Exception as e:
|
||||
@@ -166,7 +170,7 @@ class ProcurementServiceClient(BaseServiceClient):
|
||||
Dict with complete plan details
|
||||
"""
|
||||
try:
|
||||
path = f"/tenants/{tenant_id}/replenishment-plans/{plan_id}"
|
||||
path = f"/tenants/{tenant_id}/procurement/replenishment-plans/{plan_id}"
|
||||
|
||||
logger.debug("Getting replenishment plan",
|
||||
tenant_id=tenant_id, plan_id=plan_id)
|
||||
@@ -199,7 +203,7 @@ class ProcurementServiceClient(BaseServiceClient):
|
||||
List of plan summaries
|
||||
"""
|
||||
try:
|
||||
path = f"/tenants/{tenant_id}/replenishment-plans"
|
||||
path = f"/tenants/{tenant_id}/procurement/operations/replenishment-plans"
|
||||
params = {"skip": skip, "limit": limit}
|
||||
if status:
|
||||
params["status"] = status
|
||||
@@ -250,7 +254,7 @@ class ProcurementServiceClient(BaseServiceClient):
|
||||
- stockout_risk: Risk level (low/medium/high/critical)
|
||||
"""
|
||||
try:
|
||||
path = f"/tenants/{tenant_id}/replenishment-plans/inventory-projections/project"
|
||||
path = f"/tenants/{tenant_id}/procurement/operations/replenishment-plans/inventory-projections/project"
|
||||
payload = {
|
||||
"ingredient_id": ingredient_id,
|
||||
"ingredient_name": ingredient_name,
|
||||
@@ -264,7 +268,9 @@ class ProcurementServiceClient(BaseServiceClient):
|
||||
logger.info("Projecting inventory",
|
||||
tenant_id=tenant_id, ingredient_id=ingredient_id)
|
||||
|
||||
response = await self._post(path, json=payload)
|
||||
# Remove tenant_id from path since it's passed as separate parameter
|
||||
endpoint = f"procurement/operations/replenishment-plans/inventory-projections/project"
|
||||
response = await self.post(endpoint, data=payload, tenant_id=tenant_id)
|
||||
return response
|
||||
|
||||
except Exception as e:
|
||||
@@ -296,7 +302,7 @@ class ProcurementServiceClient(BaseServiceClient):
|
||||
List of inventory projections
|
||||
"""
|
||||
try:
|
||||
path = f"/tenants/{tenant_id}/replenishment-plans/inventory-projections"
|
||||
path = f"/tenants/{tenant_id}/procurement/operations/replenishment-plans/inventory-projections"
|
||||
params = {
|
||||
"skip": skip,
|
||||
"limit": limit,
|
||||
@@ -345,7 +351,7 @@ class ProcurementServiceClient(BaseServiceClient):
|
||||
- reasoning: Explanation
|
||||
"""
|
||||
try:
|
||||
path = f"/tenants/{tenant_id}/replenishment-plans/safety-stock/calculate"
|
||||
path = f"/tenants/{tenant_id}/procurement/operations/replenishment-plans/safety-stock/calculate"
|
||||
payload = {
|
||||
"ingredient_id": ingredient_id,
|
||||
"daily_demands": daily_demands,
|
||||
@@ -353,7 +359,9 @@ class ProcurementServiceClient(BaseServiceClient):
|
||||
"service_level": service_level
|
||||
}
|
||||
|
||||
response = await self._post(path, json=payload)
|
||||
# Remove tenant_id from path since it's passed as separate parameter
|
||||
endpoint = f"procurement/operations/replenishment-plans/safety-stock/calculate"
|
||||
response = await self.post(endpoint, data=payload, tenant_id=tenant_id)
|
||||
return response
|
||||
|
||||
except Exception as e:
|
||||
@@ -391,7 +399,7 @@ class ProcurementServiceClient(BaseServiceClient):
|
||||
- diversification_applied: Whether diversification was applied
|
||||
"""
|
||||
try:
|
||||
path = f"/tenants/{tenant_id}/replenishment-plans/supplier-selections/evaluate"
|
||||
path = f"/tenants/{tenant_id}/procurement/operations/replenishment-plans/supplier-selections/evaluate"
|
||||
payload = {
|
||||
"ingredient_id": ingredient_id,
|
||||
"ingredient_name": ingredient_name,
|
||||
@@ -399,7 +407,9 @@ class ProcurementServiceClient(BaseServiceClient):
|
||||
"supplier_options": supplier_options
|
||||
}
|
||||
|
||||
response = await self._post(path, json=payload)
|
||||
# Remove tenant_id from path since it's passed as separate parameter
|
||||
endpoint = f"procurement/operations/replenishment-plans/supplier-selections/evaluate"
|
||||
response = await self.post(endpoint, data=payload, tenant_id=tenant_id)
|
||||
return response
|
||||
|
||||
except Exception as e:
|
||||
@@ -429,7 +439,7 @@ class ProcurementServiceClient(BaseServiceClient):
|
||||
List of supplier allocations
|
||||
"""
|
||||
try:
|
||||
path = f"/tenants/{tenant_id}/replenishment-plans/supplier-allocations"
|
||||
path = f"/tenants/{tenant_id}/procurement/operations/replenishment-plans/supplier-allocations"
|
||||
params = {"skip": skip, "limit": limit}
|
||||
if requirement_id:
|
||||
params["requirement_id"] = requirement_id
|
||||
@@ -470,7 +480,7 @@ class ProcurementServiceClient(BaseServiceClient):
|
||||
- stockout_prevention_rate: Effectiveness metric
|
||||
"""
|
||||
try:
|
||||
path = f"/tenants/{tenant_id}/replenishment-plans/analytics"
|
||||
path = f"/tenants/{tenant_id}/procurement/analytics/replenishment-plans"
|
||||
params = {}
|
||||
if start_date:
|
||||
params["start_date"] = start_date
|
||||
@@ -484,3 +494,82 @@ class ProcurementServiceClient(BaseServiceClient):
|
||||
logger.error("Error getting replenishment analytics",
|
||||
tenant_id=tenant_id, error=str(e))
|
||||
return None
|
||||
|
||||
# ================================================================
|
||||
# ML INSIGHTS: Supplier Analysis and Price Forecasting
|
||||
# ================================================================
|
||||
|
||||
async def trigger_supplier_analysis(
|
||||
self,
|
||||
tenant_id: str,
|
||||
supplier_ids: Optional[List[str]] = None,
|
||||
lookback_days: int = 180,
|
||||
min_orders: int = 10
|
||||
) -> Optional[Dict[str, Any]]:
|
||||
"""
|
||||
Trigger supplier performance analysis.
|
||||
|
||||
Args:
|
||||
tenant_id: Tenant UUID
|
||||
supplier_ids: Specific supplier IDs to analyze. If None, analyzes all suppliers
|
||||
lookback_days: Days of historical orders to analyze (30-730)
|
||||
min_orders: Minimum orders required for analysis (5-100)
|
||||
|
||||
Returns:
|
||||
Dict with analysis results including insights posted
|
||||
"""
|
||||
try:
|
||||
data = {
|
||||
"supplier_ids": supplier_ids,
|
||||
"lookback_days": lookback_days,
|
||||
"min_orders": min_orders
|
||||
}
|
||||
result = await self.post("procurement/ml/insights/analyze-suppliers", data=data, tenant_id=tenant_id)
|
||||
if result:
|
||||
logger.info("Triggered supplier analysis",
|
||||
suppliers_analyzed=result.get('suppliers_analyzed', 0),
|
||||
insights_posted=result.get('total_insights_posted', 0),
|
||||
tenant_id=tenant_id)
|
||||
return result
|
||||
except Exception as e:
|
||||
logger.error("Error triggering supplier analysis",
|
||||
error=str(e), tenant_id=tenant_id)
|
||||
return None
|
||||
|
||||
async def trigger_price_forecasting(
|
||||
self,
|
||||
tenant_id: str,
|
||||
ingredient_ids: Optional[List[str]] = None,
|
||||
lookback_days: int = 180,
|
||||
forecast_horizon_days: int = 30
|
||||
) -> Optional[Dict[str, Any]]:
|
||||
"""
|
||||
Trigger price forecasting for procurement ingredients.
|
||||
|
||||
Args:
|
||||
tenant_id: Tenant UUID
|
||||
ingredient_ids: Specific ingredient IDs to forecast. If None, forecasts all ingredients
|
||||
lookback_days: Days of historical price data to analyze (90-730)
|
||||
forecast_horizon_days: Days to forecast ahead (7-90)
|
||||
|
||||
Returns:
|
||||
Dict with forecasting results including insights posted
|
||||
"""
|
||||
try:
|
||||
data = {
|
||||
"ingredient_ids": ingredient_ids,
|
||||
"lookback_days": lookback_days,
|
||||
"forecast_horizon_days": forecast_horizon_days
|
||||
}
|
||||
result = await self.post("procurement/ml/insights/forecast-prices", data=data, tenant_id=tenant_id)
|
||||
if result:
|
||||
logger.info("Triggered price forecasting",
|
||||
ingredients_forecasted=result.get('ingredients_forecasted', 0),
|
||||
insights_posted=result.get('total_insights_posted', 0),
|
||||
buy_now_recommendations=result.get('buy_now_recommendations', 0),
|
||||
tenant_id=tenant_id)
|
||||
return result
|
||||
except Exception as e:
|
||||
logger.error("Error triggering price forecasting",
|
||||
error=str(e), tenant_id=tenant_id)
|
||||
return None
|
||||
|
||||
Reference in New Issue
Block a user