Improve the frontend and repository layer

This commit is contained in:
Urtzi Alfaro
2025-10-23 07:44:54 +02:00
parent 8d30172483
commit 07c33fa578
112 changed files with 14726 additions and 2733 deletions

View File

@@ -0,0 +1,374 @@
# ================================================================
# 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)}"
)