294 lines
9.6 KiB
Python
294 lines
9.6 KiB
Python
"""
|
|
Production Service - Sustainability API
|
|
Exposes production-specific sustainability metrics following microservices principles
|
|
Each service owns its domain data
|
|
"""
|
|
|
|
from datetime import datetime, timedelta
|
|
from typing import Optional
|
|
from uuid import UUID
|
|
from fastapi import APIRouter, Depends, Path, Query, Request
|
|
import structlog
|
|
|
|
from shared.auth.decorators import get_current_user_dep
|
|
from app.services.production_service import ProductionService
|
|
from shared.routing import RouteBuilder
|
|
|
|
logger = structlog.get_logger()
|
|
|
|
# Create route builder for consistent URL structure
|
|
route_builder = RouteBuilder('production')
|
|
|
|
router = APIRouter(tags=["production-sustainability"])
|
|
|
|
|
|
def get_production_service(request: Request) -> ProductionService:
|
|
"""Dependency injection for production service"""
|
|
from app.core.database import database_manager
|
|
from app.core.config import settings
|
|
notification_service = getattr(request.app.state, 'notification_service', None)
|
|
return ProductionService(database_manager, settings, notification_service)
|
|
|
|
|
|
@router.get(
|
|
"/api/v1/tenants/{tenant_id}/production/sustainability/waste-metrics",
|
|
response_model=dict,
|
|
summary="Get production waste metrics",
|
|
description="""
|
|
Returns production-specific waste metrics for sustainability tracking.
|
|
|
|
This endpoint is part of the microservices architecture where each service
|
|
owns its domain data. Frontend aggregates data from multiple services.
|
|
|
|
Metrics include:
|
|
- Total production waste from batches (waste_quantity + defect_quantity)
|
|
- Production volumes (planned vs actual)
|
|
- Waste breakdown by defect type
|
|
- AI-assisted batch tracking
|
|
"""
|
|
)
|
|
async def get_production_waste_metrics(
|
|
tenant_id: UUID = Path(..., description="Tenant ID"),
|
|
start_date: Optional[datetime] = Query(None, description="Start date for metrics (default: 30 days ago)"),
|
|
end_date: Optional[datetime] = Query(None, description="End date for metrics (default: now)"),
|
|
current_user: dict = Depends(get_current_user_dep),
|
|
production_service: ProductionService = Depends(get_production_service)
|
|
):
|
|
"""
|
|
Get production waste metrics for sustainability dashboard
|
|
|
|
Returns production-specific metrics that frontend will aggregate with
|
|
inventory metrics for complete sustainability picture.
|
|
"""
|
|
try:
|
|
# Set default dates
|
|
if not end_date:
|
|
end_date = datetime.now()
|
|
if not start_date:
|
|
start_date = end_date - timedelta(days=30)
|
|
|
|
# Get waste analytics from production service
|
|
waste_data = await production_service.get_waste_analytics(
|
|
tenant_id=tenant_id,
|
|
start_date=start_date,
|
|
end_date=end_date
|
|
)
|
|
|
|
# Enrich with metadata
|
|
response = {
|
|
**waste_data,
|
|
"service": "production",
|
|
"period": {
|
|
"start_date": start_date.isoformat(),
|
|
"end_date": end_date.isoformat(),
|
|
"days": (end_date - start_date).days
|
|
},
|
|
"metadata": {
|
|
"data_source": "production_batches",
|
|
"calculation_method": "SUM(waste_quantity + defect_quantity)",
|
|
"filters_applied": {
|
|
"status": ["COMPLETED", "QUALITY_CHECK"],
|
|
"date_range": f"{start_date.date()} to {end_date.date()}"
|
|
}
|
|
}
|
|
}
|
|
|
|
logger.info(
|
|
"Production waste metrics retrieved",
|
|
tenant_id=str(tenant_id),
|
|
total_waste_kg=waste_data.get('total_production_waste', 0),
|
|
period_days=(end_date - start_date).days,
|
|
user_id=current_user.get('user_id')
|
|
)
|
|
|
|
return response
|
|
|
|
except Exception as e:
|
|
logger.error(
|
|
"Error getting production waste metrics",
|
|
tenant_id=str(tenant_id),
|
|
error=str(e)
|
|
)
|
|
raise
|
|
|
|
|
|
@router.get(
|
|
"/api/v1/tenants/{tenant_id}/production/sustainability/baseline",
|
|
response_model=dict,
|
|
summary="Get production baseline metrics",
|
|
description="""
|
|
Returns baseline production metrics from the first 90 days of operation.
|
|
|
|
Used by frontend to calculate SDG 12.3 compliance (waste reduction targets).
|
|
If tenant has less than 90 days of data, returns industry average baseline.
|
|
"""
|
|
)
|
|
async def get_production_baseline(
|
|
tenant_id: UUID = Path(..., description="Tenant ID"),
|
|
current_user: dict = Depends(get_current_user_dep),
|
|
production_service: ProductionService = Depends(get_production_service)
|
|
):
|
|
"""
|
|
Get baseline production metrics for SDG compliance calculations
|
|
|
|
Frontend uses this to calculate:
|
|
- Waste reduction percentage vs baseline
|
|
- Progress toward SDG 12.3 targets
|
|
- Grant eligibility based on improvement
|
|
"""
|
|
try:
|
|
baseline_data = await production_service.get_baseline_metrics(tenant_id)
|
|
|
|
# Add metadata
|
|
response = {
|
|
**baseline_data,
|
|
"service": "production",
|
|
"metadata": {
|
|
"baseline_period_days": 90,
|
|
"calculation_method": "First 90 days of production data",
|
|
"fallback": "Industry average (25%) if insufficient data"
|
|
}
|
|
}
|
|
|
|
logger.info(
|
|
"Production baseline metrics retrieved",
|
|
tenant_id=str(tenant_id),
|
|
has_baseline=baseline_data.get('has_baseline', False),
|
|
baseline_waste_pct=baseline_data.get('waste_percentage'),
|
|
user_id=current_user.get('user_id')
|
|
)
|
|
|
|
return response
|
|
|
|
except Exception as e:
|
|
logger.error(
|
|
"Error getting production baseline",
|
|
tenant_id=str(tenant_id),
|
|
error=str(e)
|
|
)
|
|
raise
|
|
|
|
|
|
@router.get(
|
|
"/api/v1/tenants/{tenant_id}/production/sustainability/ai-impact",
|
|
response_model=dict,
|
|
summary="Get AI waste reduction impact",
|
|
description="""
|
|
Analyzes the impact of AI-assisted production on waste reduction.
|
|
|
|
Compares waste rates between:
|
|
- AI-assisted batches (with is_ai_assisted=true)
|
|
- Manual batches (is_ai_assisted=false)
|
|
|
|
Shows ROI of AI features for sustainability.
|
|
"""
|
|
)
|
|
async def get_ai_waste_impact(
|
|
tenant_id: UUID = Path(..., description="Tenant ID"),
|
|
start_date: Optional[datetime] = Query(None, description="Start date (default: 30 days ago)"),
|
|
end_date: Optional[datetime] = Query(None, description="End date (default: now)"),
|
|
current_user: dict = Depends(get_current_user_dep),
|
|
production_service: ProductionService = Depends(get_production_service)
|
|
):
|
|
"""
|
|
Get AI impact on waste reduction
|
|
|
|
Frontend uses this to showcase:
|
|
- Value proposition of AI features
|
|
- Waste avoided through AI assistance
|
|
- Financial ROI of AI investment
|
|
"""
|
|
try:
|
|
# Set default dates
|
|
if not end_date:
|
|
end_date = datetime.now()
|
|
if not start_date:
|
|
start_date = end_date - timedelta(days=30)
|
|
|
|
# Get AI impact analytics (we'll implement this)
|
|
ai_impact = await production_service.get_ai_waste_impact(
|
|
tenant_id=tenant_id,
|
|
start_date=start_date,
|
|
end_date=end_date
|
|
)
|
|
|
|
logger.info(
|
|
"AI waste impact retrieved",
|
|
tenant_id=str(tenant_id),
|
|
ai_waste_reduction_pct=ai_impact.get('waste_reduction_percentage'),
|
|
user_id=current_user.get('user_id')
|
|
)
|
|
|
|
return ai_impact
|
|
|
|
except Exception as e:
|
|
logger.error(
|
|
"Error getting AI waste impact",
|
|
tenant_id=str(tenant_id),
|
|
error=str(e)
|
|
)
|
|
raise
|
|
|
|
|
|
@router.get(
|
|
"/api/v1/tenants/{tenant_id}/production/sustainability/summary",
|
|
response_model=dict,
|
|
summary="Get production sustainability summary",
|
|
description="""
|
|
Quick summary endpoint combining all production sustainability metrics.
|
|
|
|
Useful for dashboard widgets that need overview data without multiple calls.
|
|
"""
|
|
)
|
|
async def get_production_sustainability_summary(
|
|
tenant_id: UUID = Path(..., description="Tenant ID"),
|
|
days: int = Query(30, ge=7, le=365, description="Number of days to analyze"),
|
|
current_user: dict = Depends(get_current_user_dep),
|
|
production_service: ProductionService = Depends(get_production_service)
|
|
):
|
|
"""
|
|
Get comprehensive production sustainability summary
|
|
|
|
Combines waste metrics, baseline, and AI impact in one response.
|
|
Optimized for dashboard widgets.
|
|
"""
|
|
try:
|
|
end_date = datetime.now()
|
|
start_date = end_date - timedelta(days=days)
|
|
|
|
# Get all metrics in parallel (within service)
|
|
waste_data = await production_service.get_waste_analytics(tenant_id, start_date, end_date)
|
|
baseline_data = await production_service.get_baseline_metrics(tenant_id)
|
|
|
|
# Try to get AI impact (may not be available for all tenants)
|
|
try:
|
|
ai_impact = await production_service.get_ai_waste_impact(tenant_id, start_date, end_date)
|
|
except:
|
|
ai_impact = {"available": False}
|
|
|
|
summary = {
|
|
"service": "production",
|
|
"period_days": days,
|
|
"waste_metrics": waste_data,
|
|
"baseline": baseline_data,
|
|
"ai_impact": ai_impact,
|
|
"last_updated": datetime.now().isoformat()
|
|
}
|
|
|
|
logger.info(
|
|
"Production sustainability summary retrieved",
|
|
tenant_id=str(tenant_id),
|
|
period_days=days,
|
|
user_id=current_user.get('user_id')
|
|
)
|
|
|
|
return summary
|
|
|
|
except Exception as e:
|
|
logger.error(
|
|
"Error getting production sustainability summary",
|
|
tenant_id=str(tenant_id),
|
|
error=str(e)
|
|
)
|
|
raise
|