demo seed change 7

This commit is contained in:
Urtzi Alfaro
2025-12-15 13:39:33 +01:00
parent 46bd4f77b6
commit 5642b5a0c0
14 changed files with 5653 additions and 780 deletions

View File

@@ -388,6 +388,7 @@ async def clone_demo_data(
quality_score=batch_data.get('quality_score'),
waste_quantity=batch_data.get('waste_quantity'),
defect_quantity=batch_data.get('defect_quantity'),
waste_defect_type=batch_data.get('waste_defect_type'),
equipment_used=batch_data.get('equipment_used'),
staff_assigned=batch_data.get('staff_assigned'),
station_id=batch_data.get('station_id'),
@@ -395,6 +396,7 @@ async def clone_demo_data(
forecast_id=batch_data.get('forecast_id'),
is_rush_order=batch_data.get('is_rush_order', False),
is_special_recipe=batch_data.get('is_special_recipe', False),
is_ai_assisted=batch_data.get('is_ai_assisted', False),
production_notes=batch_data.get('production_notes'),
quality_notes=batch_data.get('quality_notes'),
delay_reason=batch_data.get('delay_reason'),

View File

@@ -0,0 +1,293 @@
"""
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