Files
bakery-ia/shared/clients/recipes_client.py

294 lines
14 KiB
Python
Raw Normal View History

2025-08-21 20:28:14 +02:00
# shared/clients/recipes_client.py
"""
Recipes Service Client for Inter-Service Communication
Provides access to recipe and ingredient requirements from other services
"""
import structlog
from typing import Dict, Any, Optional, List
from uuid import UUID
from shared.clients.base_service_client import BaseServiceClient
from shared.config.base import BaseServiceSettings
logger = structlog.get_logger()
class RecipesServiceClient(BaseServiceClient):
"""Client for communicating with the Recipes Service"""
2025-11-05 13:34:56 +01:00
def __init__(self, config: BaseServiceSettings, calling_service_name: str = "unknown"):
super().__init__(calling_service_name, config)
2025-08-21 20:28:14 +02:00
def get_service_base_path(self) -> str:
return "/api/v1"
# ================================================================
# RECIPE MANAGEMENT
# ================================================================
async def get_recipe_by_id(self, tenant_id: str, recipe_id: str) -> Optional[Dict[str, Any]]:
"""Get recipe details by ID"""
try:
2025-10-06 15:27:01 +02:00
result = await self.get(f"recipes/recipes/{recipe_id}", tenant_id=tenant_id)
2025-08-21 20:28:14 +02:00
if result:
logger.info("Retrieved recipe details from recipes service",
recipe_id=recipe_id, tenant_id=tenant_id)
return result
except Exception as e:
logger.error("Error getting recipe details",
error=str(e), recipe_id=recipe_id, tenant_id=tenant_id)
return None
async def get_recipes_by_product_ids(self, tenant_id: str, product_ids: List[str]) -> Optional[List[Dict[str, Any]]]:
"""Get recipes for multiple products"""
try:
params = {"product_ids": ",".join(product_ids)}
2025-10-06 15:27:01 +02:00
result = await self.get("recipes/recipes/by-products", tenant_id=tenant_id, params=params)
2025-08-21 20:28:14 +02:00
recipes = result.get('recipes', []) if result else []
logger.info("Retrieved recipes by product IDs from recipes service",
product_ids_count=len(product_ids),
recipes_count=len(recipes),
tenant_id=tenant_id)
return recipes
except Exception as e:
logger.error("Error getting recipes by product IDs",
error=str(e), tenant_id=tenant_id)
return []
async def get_all_recipes(self, tenant_id: str, is_active: Optional[bool] = True) -> Optional[List[Dict[str, Any]]]:
"""Get all recipes for a tenant"""
try:
params = {}
if is_active is not None:
params["is_active"] = is_active
result = await self.get_paginated("recipes", tenant_id=tenant_id, params=params)
logger.info("Retrieved all recipes from recipes service",
recipes_count=len(result), tenant_id=tenant_id)
return result
except Exception as e:
logger.error("Error getting all recipes",
error=str(e), tenant_id=tenant_id)
return []
# ================================================================
# INGREDIENT REQUIREMENTS
# ================================================================
async def get_recipe_requirements(self, tenant_id: str, recipe_ids: Optional[List[str]] = None) -> Optional[Dict[str, Any]]:
"""Get ingredient requirements for recipes"""
try:
params = {}
if recipe_ids:
params["recipe_ids"] = ",".join(recipe_ids)
2025-10-06 15:27:01 +02:00
result = await self.get("recipes/requirements", tenant_id=tenant_id, params=params)
2025-08-21 20:28:14 +02:00
if result:
logger.info("Retrieved recipe requirements from recipes service",
recipe_ids_count=len(recipe_ids) if recipe_ids else 0,
tenant_id=tenant_id)
return result
except Exception as e:
logger.error("Error getting recipe requirements",
error=str(e), tenant_id=tenant_id)
return None
async def get_ingredient_requirements(self, tenant_id: str, product_ids: Optional[List[str]] = None) -> Optional[Dict[str, Any]]:
"""Get ingredient requirements for production planning"""
try:
params = {}
if product_ids:
params["product_ids"] = ",".join(product_ids)
2025-10-06 15:27:01 +02:00
result = await self.get("recipes/ingredient-requirements", tenant_id=tenant_id, params=params)
2025-08-21 20:28:14 +02:00
if result:
logger.info("Retrieved ingredient requirements from recipes service",
product_ids_count=len(product_ids) if product_ids else 0,
tenant_id=tenant_id)
return result
except Exception as e:
logger.error("Error getting ingredient requirements",
error=str(e), tenant_id=tenant_id)
return None
async def calculate_ingredients_for_quantity(self, tenant_id: str, recipe_id: str, quantity: float) -> Optional[Dict[str, Any]]:
"""Calculate ingredient quantities needed for a specific production quantity"""
try:
data = {
"recipe_id": recipe_id,
"quantity": quantity
}
2025-10-06 15:27:01 +02:00
result = await self.post("recipes/operations/calculate-ingredients", data=data, tenant_id=tenant_id)
2025-08-21 20:28:14 +02:00
if result:
logger.info("Calculated ingredient quantities from recipes service",
recipe_id=recipe_id, quantity=quantity, tenant_id=tenant_id)
return result
except Exception as e:
logger.error("Error calculating ingredient quantities",
error=str(e), recipe_id=recipe_id, tenant_id=tenant_id)
return None
async def calculate_batch_ingredients(self, tenant_id: str, production_requests: List[Dict[str, Any]]) -> Optional[Dict[str, Any]]:
"""Calculate total ingredient requirements for multiple production batches"""
try:
data = {"production_requests": production_requests}
2025-10-06 15:27:01 +02:00
result = await self.post("recipes/operations/calculate-batch-ingredients", data=data, tenant_id=tenant_id)
2025-08-21 20:28:14 +02:00
if result:
logger.info("Calculated batch ingredient requirements from recipes service",
batches_count=len(production_requests), tenant_id=tenant_id)
return result
except Exception as e:
logger.error("Error calculating batch ingredient requirements",
error=str(e), tenant_id=tenant_id)
return None
# ================================================================
# PRODUCTION SUPPORT
# ================================================================
async def get_production_instructions(self, tenant_id: str, recipe_id: str) -> Optional[Dict[str, Any]]:
"""Get detailed production instructions for a recipe"""
try:
2025-10-06 15:27:01 +02:00
result = await self.get(f"recipes/recipes/{recipe_id}/production-instructions", tenant_id=tenant_id)
2025-08-21 20:28:14 +02:00
if result:
logger.info("Retrieved production instructions from recipes service",
recipe_id=recipe_id, tenant_id=tenant_id)
return result
except Exception as e:
logger.error("Error getting production instructions",
error=str(e), recipe_id=recipe_id, tenant_id=tenant_id)
return None
async def get_recipe_yield_info(self, tenant_id: str, recipe_id: str) -> Optional[Dict[str, Any]]:
"""Get yield information for a recipe"""
try:
2025-10-06 15:27:01 +02:00
result = await self.get(f"recipes/recipes/{recipe_id}/yield", tenant_id=tenant_id)
2025-08-21 20:28:14 +02:00
if result:
logger.info("Retrieved recipe yield info from recipes service",
recipe_id=recipe_id, tenant_id=tenant_id)
return result
except Exception as e:
logger.error("Error getting recipe yield info",
error=str(e), recipe_id=recipe_id, tenant_id=tenant_id)
return None
async def validate_recipe_feasibility(self, tenant_id: str, recipe_id: str, quantity: float) -> Optional[Dict[str, Any]]:
"""Validate if a recipe can be produced in the requested quantity"""
try:
data = {
"recipe_id": recipe_id,
"quantity": quantity
}
2025-10-06 15:27:01 +02:00
result = await self.post("recipes/operations/validate-feasibility", data=data, tenant_id=tenant_id)
2025-08-21 20:28:14 +02:00
if result:
logger.info("Validated recipe feasibility from recipes service",
recipe_id=recipe_id, quantity=quantity, tenant_id=tenant_id)
return result
except Exception as e:
logger.error("Error validating recipe feasibility",
error=str(e), recipe_id=recipe_id, tenant_id=tenant_id)
return None
# ================================================================
# ANALYTICS AND OPTIMIZATION
# ================================================================
async def get_recipe_cost_analysis(self, tenant_id: str, recipe_id: str) -> Optional[Dict[str, Any]]:
"""Get cost analysis for a recipe"""
try:
2025-10-06 15:27:01 +02:00
result = await self.get(f"recipes/recipes/{recipe_id}/cost-analysis", tenant_id=tenant_id)
2025-08-21 20:28:14 +02:00
if result:
logger.info("Retrieved recipe cost analysis from recipes service",
recipe_id=recipe_id, tenant_id=tenant_id)
return result
except Exception as e:
logger.error("Error getting recipe cost analysis",
error=str(e), recipe_id=recipe_id, tenant_id=tenant_id)
return None
async def optimize_production_batch(self, tenant_id: str, requirements: List[Dict[str, Any]]) -> Optional[Dict[str, Any]]:
"""Optimize production batch to minimize waste and cost"""
try:
data = {"requirements": requirements}
2025-10-06 15:27:01 +02:00
result = await self.post("recipes/operations/optimize-batch", data=data, tenant_id=tenant_id)
2025-08-21 20:28:14 +02:00
if result:
logger.info("Optimized production batch from recipes service",
requirements_count=len(requirements), tenant_id=tenant_id)
return result
except Exception as e:
logger.error("Error optimizing production batch",
error=str(e), tenant_id=tenant_id)
return None
# ================================================================
# DASHBOARD AND ANALYTICS
# ================================================================
async def get_dashboard_summary(self, tenant_id: str) -> Optional[Dict[str, Any]]:
"""Get recipes dashboard summary data"""
try:
2025-10-06 15:27:01 +02:00
result = await self.get("recipes/dashboard/summary", tenant_id=tenant_id)
2025-08-21 20:28:14 +02:00
if result:
logger.info("Retrieved recipes dashboard summary",
tenant_id=tenant_id)
return result
except Exception as e:
logger.error("Error getting recipes dashboard summary",
error=str(e), tenant_id=tenant_id)
return None
async def get_popular_recipes(self, tenant_id: str, period: str = "last_30_days") -> Optional[List[Dict[str, Any]]]:
"""Get most popular recipes based on production frequency"""
try:
params = {"period": period}
2025-10-06 15:27:01 +02:00
result = await self.get("recipes/analytics/popular-recipes", tenant_id=tenant_id, params=params)
2025-08-21 20:28:14 +02:00
recipes = result.get('recipes', []) if result else []
logger.info("Retrieved popular recipes from recipes service",
period=period, recipes_count=len(recipes), tenant_id=tenant_id)
return recipes
except Exception as e:
logger.error("Error getting popular recipes",
error=str(e), tenant_id=tenant_id)
return []
# ================================================================
# COUNT AND STATISTICS
# ================================================================
async def count_recipes(self, tenant_id: str) -> int:
"""
Get the count of recipes for a tenant
Used for subscription limit tracking
Returns:
int: Number of recipes for the tenant
"""
try:
result = await self.get("recipes/count", tenant_id=tenant_id)
count = result.get('count', 0) if result else 0
logger.info("Retrieved recipe count from recipes service",
count=count, tenant_id=tenant_id)
return count
except Exception as e:
logger.error("Error getting recipe count",
error=str(e), tenant_id=tenant_id)
return 0
2025-08-21 20:28:14 +02:00
# ================================================================
# UTILITY METHODS
# ================================================================
2025-08-21 20:28:14 +02:00
async def health_check(self) -> bool:
"""Check if recipes service is healthy"""
try:
result = await self.get("../health") # Health endpoint is not tenant-scoped
return result is not None
except Exception as e:
logger.error("Recipes service health check failed", error=str(e))
return False
# Factory function for dependency injection
def create_recipes_client(config: BaseServiceSettings) -> RecipesServiceClient:
"""Create recipes service client instance"""
return RecipesServiceClient(config)