Start fixing forecast service 16
This commit is contained in:
@@ -46,6 +46,7 @@ async def lifespan(app: FastAPI):
|
|||||||
# Register custom metrics
|
# Register custom metrics
|
||||||
metrics_collector.register_counter("forecasts_generated_total", "Total forecasts generated")
|
metrics_collector.register_counter("forecasts_generated_total", "Total forecasts generated")
|
||||||
metrics_collector.register_counter("predictions_served_total", "Total predictions served")
|
metrics_collector.register_counter("predictions_served_total", "Total predictions served")
|
||||||
|
metrics_collector.register_counter("prediction_errors_total", "Total prediction errors")
|
||||||
metrics_collector.register_histogram("forecast_processing_time_seconds", "Time to process forecast request")
|
metrics_collector.register_histogram("forecast_processing_time_seconds", "Time to process forecast request")
|
||||||
metrics_collector.register_gauge("active_models_count", "Number of active models")
|
metrics_collector.register_gauge("active_models_count", "Number of active models")
|
||||||
|
|
||||||
|
|||||||
@@ -89,16 +89,39 @@ class ForecastingService:
|
|||||||
prediction=adjusted_prediction['prediction'])
|
prediction=adjusted_prediction['prediction'])
|
||||||
|
|
||||||
return ForecastResponse(
|
return ForecastResponse(
|
||||||
id=forecast.id,
|
id=str(forecast.id),
|
||||||
forecast_date=forecast.forecast_date,
|
tenant_id=str(forecast.tenant_id),
|
||||||
product_name=forecast.product_name,
|
product_name=forecast.product_name,
|
||||||
predicted_quantity=forecast.predicted_quantity,
|
location=forecast.location,
|
||||||
|
forecast_date=forecast.forecast_date,
|
||||||
|
|
||||||
|
# Predictions
|
||||||
|
predicted_demand=forecast.predicted_demand,
|
||||||
|
confidence_lower=forecast.confidence_lower,
|
||||||
|
confidence_upper=forecast.confidence_upper,
|
||||||
confidence_level=forecast.confidence_level,
|
confidence_level=forecast.confidence_level,
|
||||||
lower_bound=forecast.lower_bound,
|
|
||||||
upper_bound=forecast.upper_bound,
|
# Model info
|
||||||
model_id=forecast.model_id,
|
model_id=str(forecast.model_id),
|
||||||
|
model_version=forecast.model_version,
|
||||||
|
algorithm=forecast.algorithm,
|
||||||
|
|
||||||
|
# Context
|
||||||
|
business_type=forecast.business_type,
|
||||||
|
is_holiday=forecast.is_holiday,
|
||||||
|
is_weekend=forecast.is_weekend,
|
||||||
|
day_of_week=forecast.day_of_week,
|
||||||
|
|
||||||
|
# External factors
|
||||||
|
weather_temperature=forecast.weather_temperature,
|
||||||
|
weather_precipitation=forecast.weather_precipitation,
|
||||||
|
weather_description=forecast.weather_description,
|
||||||
|
traffic_volume=forecast.traffic_volume,
|
||||||
|
|
||||||
|
# Metadata
|
||||||
created_at=forecast.created_at,
|
created_at=forecast.created_at,
|
||||||
external_factors=forecast.external_factors
|
processing_time_ms=forecast.processing_time_ms,
|
||||||
|
features_used=forecast.features_used
|
||||||
)
|
)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -171,7 +194,7 @@ class ForecastingService:
|
|||||||
await self._add_weather_features_with_fallbacks(features, tenant_id)
|
await self._add_weather_features_with_fallbacks(features, tenant_id)
|
||||||
|
|
||||||
# Add traffic data with fallbacks
|
# Add traffic data with fallbacks
|
||||||
# await self._add_traffic_features_with_fallbacks(features, tenant_id)
|
await self._add_traffic_features_with_fallbacks(features, tenant_id)
|
||||||
|
|
||||||
return features
|
return features
|
||||||
|
|
||||||
@@ -248,23 +271,23 @@ class ForecastingService:
|
|||||||
) -> None:
|
) -> None:
|
||||||
"""Add traffic features with fallbacks"""
|
"""Add traffic features with fallbacks"""
|
||||||
|
|
||||||
try:
|
# try:
|
||||||
traffic_data = await self.data_client.get_traffic_data(
|
# traffic_data = await self.data_client.get_traffic_data(
|
||||||
tenant_id=tenant_id,
|
# tenant_id=tenant_id,
|
||||||
latitude=40.4168,
|
# latitude=40.4168,
|
||||||
longitude=-3.7038
|
# longitude=-3.7038
|
||||||
)
|
# )
|
||||||
|
#
|
||||||
|
# if traffic_data:
|
||||||
|
# features.update({
|
||||||
|
# "traffic_volume": traffic_data.get("traffic_volume", 100),
|
||||||
|
# "pedestrian_count": traffic_data.get("pedestrian_count", 50),
|
||||||
|
# })
|
||||||
|
# logger.info("Traffic data acquired successfully", tenant_id=tenant_id)
|
||||||
|
# return
|
||||||
|
|
||||||
if traffic_data:
|
# except Exception as e:
|
||||||
features.update({
|
# logger.warning("Traffic data acquisition failed", error=str(e))
|
||||||
"traffic_volume": traffic_data.get("traffic_volume", 100),
|
|
||||||
"pedestrian_count": traffic_data.get("pedestrian_count", 50),
|
|
||||||
})
|
|
||||||
logger.info("Traffic data acquired successfully", tenant_id=tenant_id)
|
|
||||||
return
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
logger.warning("Traffic data acquisition failed", error=str(e))
|
|
||||||
|
|
||||||
# Fallback: Use typical values based on day of week
|
# Fallback: Use typical values based on day of week
|
||||||
day_of_week = features["day_of_week"]
|
day_of_week = features["day_of_week"]
|
||||||
@@ -273,6 +296,7 @@ class ForecastingService:
|
|||||||
features.update({
|
features.update({
|
||||||
"traffic_volume": int(100 * weekend_factor),
|
"traffic_volume": int(100 * weekend_factor),
|
||||||
"pedestrian_count": int(50 * weekend_factor),
|
"pedestrian_count": int(50 * weekend_factor),
|
||||||
|
"congestion_level": 1
|
||||||
})
|
})
|
||||||
|
|
||||||
logger.warning("Using default traffic values", tenant_id=tenant_id)
|
logger.warning("Using default traffic values", tenant_id=tenant_id)
|
||||||
@@ -398,15 +422,36 @@ class ForecastingService:
|
|||||||
|
|
||||||
forecast = Forecast(
|
forecast = Forecast(
|
||||||
tenant_id=tenant_id,
|
tenant_id=tenant_id,
|
||||||
forecast_date=request.forecast_date,
|
|
||||||
product_name=request.product_name,
|
product_name=request.product_name,
|
||||||
predicted_quantity=prediction["prediction"],
|
location=request.location,
|
||||||
|
forecast_date=request.forecast_date,
|
||||||
|
|
||||||
|
# Predictions
|
||||||
|
predicted_demand=prediction['prediction'],
|
||||||
|
confidence_lower=prediction['lower_bound'],
|
||||||
|
confidence_upper=prediction['upper_bound'],
|
||||||
confidence_level=request.confidence_level,
|
confidence_level=request.confidence_level,
|
||||||
lower_bound=prediction["lower_bound"],
|
|
||||||
upper_bound=prediction["upper_bound"],
|
# Model info
|
||||||
model_id=model_data["model_id"],
|
model_id=model_data['model_id'],
|
||||||
external_factors=features,
|
model_version=model_data.get('version', '1.0'),
|
||||||
created_at=datetime.utcnow()
|
algorithm=model_data.get('algorithm', 'prophet'),
|
||||||
|
|
||||||
|
# Context
|
||||||
|
business_type=features.get('business_type', 'individual'),
|
||||||
|
is_holiday=features.get('is_holiday', False),
|
||||||
|
is_weekend=features.get('is_weekend', False),
|
||||||
|
day_of_week=features.get('day_of_week', 0),
|
||||||
|
|
||||||
|
# External factors
|
||||||
|
weather_temperature=features.get('temperature'),
|
||||||
|
weather_precipitation=features.get('precipitation'),
|
||||||
|
weather_description=features.get('weather_description'),
|
||||||
|
traffic_volume=features.get('traffic_volume'),
|
||||||
|
|
||||||
|
# Metadata
|
||||||
|
processing_time_ms=int((datetime.now() - start_time).total_seconds() * 1000),
|
||||||
|
features_used=features
|
||||||
)
|
)
|
||||||
|
|
||||||
db.add(forecast)
|
db.add(forecast)
|
||||||
|
|||||||
@@ -220,7 +220,7 @@ class PredictionService:
|
|||||||
# Add numeric features with safe conversion
|
# Add numeric features with safe conversion
|
||||||
numeric_features = [
|
numeric_features = [
|
||||||
'temperature', 'precipitation', 'humidity', 'wind_speed',
|
'temperature', 'precipitation', 'humidity', 'wind_speed',
|
||||||
'traffic_volume', 'pedestrian_count', 'pressure' # ✅ FIX: Added pressure
|
'traffic_volume', 'pedestrian_count', 'pressure'
|
||||||
]
|
]
|
||||||
|
|
||||||
for feature in numeric_features:
|
for feature in numeric_features:
|
||||||
@@ -313,15 +313,13 @@ class PredictionService:
|
|||||||
df['is_heavy_rain'] = int(precip > 10.0)
|
df['is_heavy_rain'] = int(precip > 10.0)
|
||||||
df['rain_intensity'] = int(self._get_rain_intensity(precip))
|
df['rain_intensity'] = int(self._get_rain_intensity(precip))
|
||||||
|
|
||||||
# Traffic-based features (if available)
|
# Traffic-based features
|
||||||
if 'traffic_volume' in df.columns and df['traffic_volume'].iloc[0] > 0:
|
if 'traffic_volume' in df.columns and df['traffic_volume'].iloc[0] > 0:
|
||||||
traffic = df['traffic_volume'].iloc[0]
|
traffic = df['traffic_volume'].iloc[0]
|
||||||
# Simple categorization since we don't have historical data for quantiles
|
|
||||||
df['high_traffic'] = int(traffic > 150) # Assumption based on typical values
|
df['high_traffic'] = int(traffic > 150) # Assumption based on typical values
|
||||||
df['low_traffic'] = int(traffic < 50)
|
df['low_traffic'] = int(traffic < 50)
|
||||||
df['traffic_normalized'] = float((traffic - 100) / 50) # Simple normalization
|
df['traffic_normalized'] = float((traffic - 100) / 50) # Simple normalization
|
||||||
|
df['congestion_level'] = int(min(5, max(1, traffic // 50)))
|
||||||
# ✅ FIX: Add additional traffic features that might be in training
|
|
||||||
df['traffic_squared'] = traffic ** 2
|
df['traffic_squared'] = traffic ** 2
|
||||||
df['traffic_log'] = float(np.log1p(traffic)) # log(1+traffic) to handle zeros
|
df['traffic_log'] = float(np.log1p(traffic)) # log(1+traffic) to handle zeros
|
||||||
else:
|
else:
|
||||||
@@ -330,6 +328,7 @@ class PredictionService:
|
|||||||
df['traffic_normalized'] = 0.0
|
df['traffic_normalized'] = 0.0
|
||||||
df['traffic_squared'] = 0.0
|
df['traffic_squared'] = 0.0
|
||||||
df['traffic_log'] = 0.0
|
df['traffic_log'] = 0.0
|
||||||
|
df['congestion_level'] = 1
|
||||||
|
|
||||||
# Interaction features (common in training)
|
# Interaction features (common in training)
|
||||||
if 'is_weekend' in df.columns and 'temperature' in df.columns:
|
if 'is_weekend' in df.columns and 'temperature' in df.columns:
|
||||||
|
|||||||
@@ -27,6 +27,21 @@ class BakeryDataProcessor:
|
|||||||
self.imputers = {} # Store imputers for missing value handling
|
self.imputers = {} # Store imputers for missing value handling
|
||||||
self.date_alignment_service = DateAlignmentService()
|
self.date_alignment_service = DateAlignmentService()
|
||||||
|
|
||||||
|
def _ensure_timezone_aware(self, df: pd.DataFrame, date_column: str = 'date') -> pd.DataFrame:
|
||||||
|
"""Ensure date column is timezone-aware to prevent conversion errors"""
|
||||||
|
if date_column in df.columns:
|
||||||
|
# Convert to datetime if not already
|
||||||
|
df[date_column] = pd.to_datetime(df[date_column])
|
||||||
|
|
||||||
|
# If timezone-naive, localize to UTC
|
||||||
|
if df[date_column].dt.tz is None:
|
||||||
|
df[date_column] = df[date_column].dt.tz_localize('UTC')
|
||||||
|
# If already timezone-aware but not UTC, convert to UTC
|
||||||
|
elif str(df[date_column].dt.tz) != 'UTC':
|
||||||
|
df[date_column] = df[date_column].dt.tz_convert('UTC')
|
||||||
|
|
||||||
|
return df
|
||||||
|
|
||||||
async def prepare_training_data(self,
|
async def prepare_training_data(self,
|
||||||
sales_data: pd.DataFrame,
|
sales_data: pd.DataFrame,
|
||||||
weather_data: pd.DataFrame,
|
weather_data: pd.DataFrame,
|
||||||
@@ -50,6 +65,11 @@ class BakeryDataProcessor:
|
|||||||
# Step 1: Convert and validate sales data
|
# Step 1: Convert and validate sales data
|
||||||
sales_clean = await self._process_sales_data(sales_data, product_name)
|
sales_clean = await self._process_sales_data(sales_data, product_name)
|
||||||
|
|
||||||
|
# FIX: Ensure timezone awareness before any operations
|
||||||
|
sales_clean = self._ensure_timezone_aware(sales_clean)
|
||||||
|
weather_data = self._ensure_timezone_aware(weather_data) if not weather_data.empty else weather_data
|
||||||
|
traffic_data = self._ensure_timezone_aware(traffic_data) if not traffic_data.empty else traffic_data
|
||||||
|
|
||||||
# Step 2: Apply date alignment if we have date constraints
|
# Step 2: Apply date alignment if we have date constraints
|
||||||
sales_clean = await self._apply_date_alignment(sales_clean, weather_data, traffic_data)
|
sales_clean = await self._apply_date_alignment(sales_clean, weather_data, traffic_data)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user