Files
bakery-ia/services/forecasting/app/jobs/daily_validation.py

148 lines
4.8 KiB
Python
Raw Normal View History

2025-11-18 07:17:17 +01:00
# ================================================================
# services/forecasting/app/jobs/daily_validation.py
# ================================================================
"""
Daily Validation Job
Scheduled job to validate previous day's forecasts against actual sales.
This job is called by the orchestrator as part of the daily workflow.
"""
from typing import Dict, Any, Optional
from datetime import datetime, timedelta, timezone
import structlog
import uuid
from app.services.validation_service import ValidationService
from app.core.database import database_manager
logger = structlog.get_logger()
async def daily_validation_job(
tenant_id: uuid.UUID,
orchestration_run_id: Optional[uuid.UUID] = None
) -> Dict[str, Any]:
"""
Validate yesterday's forecasts against actual sales
This function is designed to be called by the orchestrator as part of
the daily workflow (Step 5: validate_previous_forecasts).
Args:
tenant_id: Tenant identifier
orchestration_run_id: Optional orchestration run ID for tracking
Returns:
Dictionary with validation results
"""
async with database_manager.get_session() as db:
try:
logger.info(
"Starting daily validation job",
tenant_id=tenant_id,
orchestration_run_id=orchestration_run_id
)
validation_service = ValidationService(db)
# Validate yesterday's forecasts
result = await validation_service.validate_yesterday(
tenant_id=tenant_id,
orchestration_run_id=orchestration_run_id,
triggered_by="orchestrator"
)
logger.info(
"Daily validation job completed",
tenant_id=tenant_id,
validation_run_id=result.get("validation_run_id"),
forecasts_evaluated=result.get("forecasts_evaluated"),
forecasts_with_actuals=result.get("forecasts_with_actuals"),
overall_mape=result.get("overall_metrics", {}).get("mape")
)
return result
except Exception as e:
logger.error(
"Daily validation job failed",
tenant_id=tenant_id,
orchestration_run_id=orchestration_run_id,
error=str(e),
error_type=type(e).__name__
)
return {
"status": "failed",
"error": str(e),
"tenant_id": str(tenant_id),
"orchestration_run_id": str(orchestration_run_id) if orchestration_run_id else None
}
async def validate_date_range_job(
tenant_id: uuid.UUID,
start_date: datetime,
end_date: datetime,
orchestration_run_id: Optional[uuid.UUID] = None
) -> Dict[str, Any]:
"""
Validate forecasts for a specific date range
Useful for backfilling validation metrics when historical data is uploaded.
Args:
tenant_id: Tenant identifier
start_date: Start of validation period
end_date: End of validation period
orchestration_run_id: Optional orchestration run ID for tracking
Returns:
Dictionary with validation results
"""
async with database_manager.get_session() as db:
try:
logger.info(
"Starting date range validation job",
tenant_id=tenant_id,
start_date=start_date.isoformat(),
end_date=end_date.isoformat(),
orchestration_run_id=orchestration_run_id
)
validation_service = ValidationService(db)
result = await validation_service.validate_date_range(
tenant_id=tenant_id,
start_date=start_date,
end_date=end_date,
orchestration_run_id=orchestration_run_id,
triggered_by="scheduled"
)
logger.info(
"Date range validation job completed",
tenant_id=tenant_id,
validation_run_id=result.get("validation_run_id"),
forecasts_evaluated=result.get("forecasts_evaluated"),
forecasts_with_actuals=result.get("forecasts_with_actuals")
)
return result
except Exception as e:
logger.error(
"Date range validation job failed",
tenant_id=tenant_id,
start_date=start_date.isoformat(),
end_date=end_date.isoformat(),
error=str(e),
error_type=type(e).__name__
)
return {
"status": "failed",
"error": str(e),
"tenant_id": str(tenant_id),
"orchestration_run_id": str(orchestration_run_id) if orchestration_run_id else None
}