# ================================================================ # services/inventory/app/api/sustainability.py # ================================================================ """ Sustainability API endpoints for Environmental Impact & SDG Compliance Following standardized URL structure: /api/v1/tenants/{tenant_id}/sustainability/{operation} """ from datetime import datetime, timedelta from typing import Optional from uuid import UUID from fastapi import APIRouter, Depends, HTTPException, Query, Path, status from fastapi.responses import JSONResponse from sqlalchemy.ext.asyncio import AsyncSession import structlog from shared.auth.decorators import get_current_user_dep from app.core.database import get_db from app.services.sustainability_service import SustainabilityService from app.schemas.sustainability import ( SustainabilityMetrics, GrantReport, SustainabilityWidgetData, SustainabilityMetricsRequest, GrantReportRequest ) from shared.routing import RouteBuilder logger = structlog.get_logger() # Create route builder for consistent URL structure route_builder = RouteBuilder('sustainability') router = APIRouter(tags=["sustainability"]) # ===== Dependency Injection ===== async def get_sustainability_service() -> SustainabilityService: """Get sustainability service instance""" return SustainabilityService() # ===== SUSTAINABILITY ENDPOINTS ===== @router.get( "/api/v1/tenants/{tenant_id}/sustainability/metrics", response_model=SustainabilityMetrics, summary="Get Sustainability Metrics", description="Get comprehensive sustainability metrics including environmental impact, SDG compliance, and grant readiness" ) async def get_sustainability_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), sustainability_service: SustainabilityService = Depends(get_sustainability_service), db: AsyncSession = Depends(get_db) ): """ Get comprehensive sustainability metrics for the tenant. **Includes:** - Food waste metrics (production, inventory, total) - Environmental impact (CO2, water, land use) - UN SDG 12.3 compliance tracking - Waste avoided through AI predictions - Financial impact analysis - Grant program eligibility assessment **Use cases:** - Dashboard displays - Grant applications - Sustainability reporting - Compliance verification """ try: metrics = await sustainability_service.get_sustainability_metrics( db=db, tenant_id=tenant_id, start_date=start_date, end_date=end_date ) logger.info( "Sustainability metrics retrieved", tenant_id=str(tenant_id), user_id=current_user.get('user_id'), waste_reduction=metrics.get('sdg_compliance', {}).get('sdg_12_3', {}).get('reduction_achieved', 0) ) return metrics except Exception as e: logger.error( "Error getting sustainability metrics", tenant_id=str(tenant_id), error=str(e) ) raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"Failed to retrieve sustainability metrics: {str(e)}" ) @router.get( "/api/v1/tenants/{tenant_id}/sustainability/widget", response_model=SustainabilityWidgetData, summary="Get Sustainability Widget Data", description="Get simplified sustainability data optimized for dashboard widgets" ) async def get_sustainability_widget_data( tenant_id: UUID = Path(..., description="Tenant ID"), days: int = Query(30, ge=1, le=365, description="Number of days to analyze"), current_user: dict = Depends(get_current_user_dep), sustainability_service: SustainabilityService = Depends(get_sustainability_service), db: AsyncSession = Depends(get_db) ): """ Get simplified sustainability metrics for dashboard widgets. **Optimized for:** - Dashboard displays - Quick overview cards - Real-time monitoring **Returns:** - Key metrics only - Human-readable values - Status indicators """ try: end_date = datetime.now() start_date = end_date - timedelta(days=days) metrics = await sustainability_service.get_sustainability_metrics( db=db, tenant_id=tenant_id, start_date=start_date, end_date=end_date ) # Extract widget-friendly data widget_data = { 'total_waste_kg': metrics['waste_metrics']['total_waste_kg'], 'waste_reduction_percentage': metrics['sdg_compliance']['sdg_12_3']['reduction_achieved'], 'co2_saved_kg': metrics['environmental_impact']['co2_emissions']['kg'], 'water_saved_liters': metrics['environmental_impact']['water_footprint']['liters'], 'trees_equivalent': metrics['environmental_impact']['co2_emissions']['trees_to_offset'], 'sdg_status': metrics['sdg_compliance']['sdg_12_3']['status'], 'sdg_progress': metrics['sdg_compliance']['sdg_12_3']['progress_to_target'], 'grant_programs_ready': len(metrics['grant_readiness']['recommended_applications']), 'financial_savings_eur': metrics['financial_impact']['waste_cost_eur'] } logger.info( "Widget data retrieved", tenant_id=str(tenant_id), user_id=current_user.get('user_id') ) return widget_data except Exception as e: logger.error( "Error getting widget data", tenant_id=str(tenant_id), error=str(e) ) raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"Failed to retrieve widget data: {str(e)}" ) @router.post( "/api/v1/tenants/{tenant_id}/sustainability/export/grant-report", response_model=GrantReport, summary="Export Grant Application Report", description="Generate a comprehensive report formatted for grant applications" ) async def export_grant_report( tenant_id: UUID = Path(..., description="Tenant ID"), request: GrantReportRequest = None, current_user: dict = Depends(get_current_user_dep), sustainability_service: SustainabilityService = Depends(get_sustainability_service), db: AsyncSession = Depends(get_db) ): """ Generate comprehensive grant application report. **Supported grant types:** - `general`: General sustainability report - `eu_horizon`: EU Horizon Europe format - `farm_to_fork`: EU Farm to Fork Strategy - `circular_economy`: Circular Economy grants - `un_sdg`: UN SDG certification **Export formats:** - `json`: JSON format (default) - `pdf`: PDF document (future) - `csv`: CSV export (future) **Use cases:** - Grant applications - Compliance reporting - Investor presentations - Certification requests """ try: if request is None: request = GrantReportRequest() report = await sustainability_service.export_grant_report( db=db, tenant_id=tenant_id, grant_type=request.grant_type, start_date=request.start_date, end_date=request.end_date ) logger.info( "Grant report exported", tenant_id=str(tenant_id), grant_type=request.grant_type, user_id=current_user.get('user_id') ) # For now, return JSON. In future, support PDF/CSV generation if request.format == 'json': return report else: # Future: Generate PDF or CSV raise HTTPException( status_code=status.HTTP_501_NOT_IMPLEMENTED, detail=f"Export format '{request.format}' not yet implemented. Use 'json' for now." ) except Exception as e: logger.error( "Error exporting grant report", tenant_id=str(tenant_id), error=str(e) ) raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"Failed to export grant report: {str(e)}" ) @router.get( "/api/v1/tenants/{tenant_id}/sustainability/sdg-compliance", summary="Get SDG 12.3 Compliance Status", description="Get detailed UN SDG 12.3 compliance status and progress" ) async def get_sdg_compliance( tenant_id: UUID = Path(..., description="Tenant ID"), current_user: dict = Depends(get_current_user_dep), sustainability_service: SustainabilityService = Depends(get_sustainability_service), db: AsyncSession = Depends(get_db) ): """ Get detailed UN SDG 12.3 compliance information. **SDG 12.3 Target:** By 2030, halve per capita global food waste at the retail and consumer levels and reduce food losses along production and supply chains, including post-harvest losses. **Returns:** - Current compliance status - Progress toward 50% reduction target - Baseline comparison - Certification readiness - Improvement recommendations """ try: metrics = await sustainability_service.get_sustainability_metrics( db=db, tenant_id=tenant_id ) sdg_data = { 'sdg_12_3_compliance': metrics['sdg_compliance']['sdg_12_3'], 'baseline_period': metrics['sdg_compliance']['baseline_period'], 'certification_ready': metrics['sdg_compliance']['certification_ready'], 'improvement_areas': metrics['sdg_compliance']['improvement_areas'], 'current_waste': metrics['waste_metrics'], 'environmental_impact': metrics['environmental_impact'] } logger.info( "SDG compliance data retrieved", tenant_id=str(tenant_id), status=sdg_data['sdg_12_3_compliance']['status'] ) return sdg_data except Exception as e: logger.error( "Error getting SDG compliance", tenant_id=str(tenant_id), error=str(e) ) raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"Failed to retrieve SDG compliance data: {str(e)}" ) @router.get( "/api/v1/tenants/{tenant_id}/sustainability/environmental-impact", summary="Get Environmental Impact", description="Get detailed environmental impact metrics" ) async def get_environmental_impact( tenant_id: UUID = Path(..., description="Tenant ID"), days: int = Query(30, ge=1, le=365, description="Number of days to analyze"), current_user: dict = Depends(get_current_user_dep), sustainability_service: SustainabilityService = Depends(get_sustainability_service), db: AsyncSession = Depends(get_db) ): """ Get detailed environmental impact of food waste. **Metrics included:** - CO2 emissions (kg and tons) - Water footprint (liters and cubic meters) - Land use (m² and hectares) - Human-relatable equivalents (car km, showers, etc.) **Use cases:** - Sustainability reports - Marketing materials - Customer communication - ESG reporting """ try: end_date = datetime.now() start_date = end_date - timedelta(days=days) metrics = await sustainability_service.get_sustainability_metrics( db=db, tenant_id=tenant_id, start_date=start_date, end_date=end_date ) impact_data = { 'period': metrics['period'], 'waste_metrics': metrics['waste_metrics'], 'environmental_impact': metrics['environmental_impact'], 'avoided_impact': metrics['avoided_waste']['environmental_impact_avoided'], 'financial_impact': metrics['financial_impact'] } logger.info( "Environmental impact data retrieved", tenant_id=str(tenant_id), co2_kg=impact_data['environmental_impact']['co2_emissions']['kg'] ) return impact_data except Exception as e: logger.error( "Error getting environmental impact", tenant_id=str(tenant_id), error=str(e) ) raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"Failed to retrieve environmental impact: {str(e)}" )