REFACTOR external service and improve websocket training
This commit is contained in:
@@ -164,7 +164,170 @@ class PredictionService:
|
||||
except Exception:
|
||||
pass # Don't fail on metrics errors
|
||||
raise
|
||||
|
||||
|
||||
async def predict_with_weather_forecast(
|
||||
self,
|
||||
model_id: str,
|
||||
model_path: str,
|
||||
features: Dict[str, Any],
|
||||
tenant_id: str,
|
||||
days: int = 7,
|
||||
confidence_level: float = 0.8
|
||||
) -> List[Dict[str, float]]:
|
||||
"""
|
||||
Generate predictions enriched with real weather forecast data
|
||||
|
||||
This method:
|
||||
1. Loads the trained ML model
|
||||
2. Fetches real weather forecast from external service
|
||||
3. Enriches prediction features with actual forecast data
|
||||
4. Generates weather-aware predictions
|
||||
|
||||
Args:
|
||||
model_id: ID of the trained model
|
||||
model_path: Path to model file
|
||||
features: Base features for prediction
|
||||
tenant_id: Tenant ID for weather forecast
|
||||
days: Number of days to forecast
|
||||
confidence_level: Confidence level for predictions
|
||||
|
||||
Returns:
|
||||
List of predictions with weather-aware adjustments
|
||||
"""
|
||||
from app.services.data_client import data_client
|
||||
|
||||
start_time = datetime.now()
|
||||
|
||||
try:
|
||||
logger.info("Generating weather-aware predictions",
|
||||
model_id=model_id,
|
||||
days=days)
|
||||
|
||||
# Step 1: Load ML model
|
||||
model = await self._load_model(model_id, model_path)
|
||||
if not model:
|
||||
raise ValueError(f"Model {model_id} not found")
|
||||
|
||||
# Step 2: Fetch real weather forecast
|
||||
latitude = features.get('latitude', 40.4168)
|
||||
longitude = features.get('longitude', -3.7038)
|
||||
|
||||
weather_forecast = await data_client.fetch_weather_forecast(
|
||||
tenant_id=tenant_id,
|
||||
days=days,
|
||||
latitude=latitude,
|
||||
longitude=longitude
|
||||
)
|
||||
|
||||
logger.info(f"Fetched weather forecast for {len(weather_forecast)} days",
|
||||
tenant_id=tenant_id)
|
||||
|
||||
# Step 3: Generate predictions for each day with weather data
|
||||
predictions = []
|
||||
|
||||
for day_offset in range(days):
|
||||
# Get weather for this specific day
|
||||
day_weather = weather_forecast[day_offset] if day_offset < len(weather_forecast) else {}
|
||||
|
||||
# Enrich features with actual weather forecast
|
||||
enriched_features = features.copy()
|
||||
enriched_features.update({
|
||||
'temperature': day_weather.get('temperature', features.get('temperature', 20.0)),
|
||||
'precipitation': day_weather.get('precipitation', features.get('precipitation', 0.0)),
|
||||
'humidity': day_weather.get('humidity', features.get('humidity', 60.0)),
|
||||
'wind_speed': day_weather.get('wind_speed', features.get('wind_speed', 10.0)),
|
||||
'pressure': day_weather.get('pressure', features.get('pressure', 1013.0)),
|
||||
'weather_description': day_weather.get('description', 'Clear')
|
||||
})
|
||||
|
||||
# Prepare Prophet dataframe with weather features
|
||||
prophet_df = self._prepare_prophet_features(enriched_features)
|
||||
|
||||
# Generate prediction for this day
|
||||
forecast = model.predict(prophet_df)
|
||||
|
||||
prediction_value = float(forecast['yhat'].iloc[0])
|
||||
lower_bound = float(forecast['yhat_lower'].iloc[0])
|
||||
upper_bound = float(forecast['yhat_upper'].iloc[0])
|
||||
|
||||
# Apply weather-based adjustments (business rules)
|
||||
adjusted_prediction = self._apply_weather_adjustments(
|
||||
prediction_value,
|
||||
day_weather,
|
||||
features.get('product_category', 'general')
|
||||
)
|
||||
|
||||
predictions.append({
|
||||
"date": enriched_features['date'],
|
||||
"prediction": max(0, adjusted_prediction),
|
||||
"lower_bound": max(0, lower_bound),
|
||||
"upper_bound": max(0, upper_bound),
|
||||
"confidence_level": confidence_level,
|
||||
"weather": {
|
||||
"temperature": enriched_features['temperature'],
|
||||
"precipitation": enriched_features['precipitation'],
|
||||
"description": enriched_features['weather_description']
|
||||
}
|
||||
})
|
||||
|
||||
processing_time = (datetime.now() - start_time).total_seconds()
|
||||
|
||||
logger.info("Weather-aware predictions generated",
|
||||
model_id=model_id,
|
||||
days=len(predictions),
|
||||
processing_time=processing_time)
|
||||
|
||||
return predictions
|
||||
|
||||
except Exception as e:
|
||||
logger.error("Error generating weather-aware predictions",
|
||||
error=str(e),
|
||||
model_id=model_id)
|
||||
raise
|
||||
|
||||
def _apply_weather_adjustments(
|
||||
self,
|
||||
base_prediction: float,
|
||||
weather: Dict[str, Any],
|
||||
product_category: str
|
||||
) -> float:
|
||||
"""
|
||||
Apply business rules based on weather conditions
|
||||
|
||||
Adjusts predictions based on real weather forecast
|
||||
"""
|
||||
adjusted = base_prediction
|
||||
temp = weather.get('temperature', 20.0)
|
||||
precip = weather.get('precipitation', 0.0)
|
||||
|
||||
# Temperature-based adjustments
|
||||
if product_category == 'ice_cream':
|
||||
if temp > 30:
|
||||
adjusted *= 1.4 # +40% for very hot days
|
||||
elif temp > 25:
|
||||
adjusted *= 1.2 # +20% for hot days
|
||||
elif temp < 15:
|
||||
adjusted *= 0.7 # -30% for cold days
|
||||
|
||||
elif product_category == 'bread':
|
||||
if temp > 30:
|
||||
adjusted *= 0.9 # -10% for very hot days
|
||||
elif temp < 10:
|
||||
adjusted *= 1.1 # +10% for cold days
|
||||
|
||||
elif product_category == 'coffee':
|
||||
if temp < 15:
|
||||
adjusted *= 1.2 # +20% for cold days
|
||||
elif precip > 5:
|
||||
adjusted *= 1.15 # +15% for rainy days
|
||||
|
||||
# Precipitation-based adjustments
|
||||
if precip > 10: # Heavy rain
|
||||
if product_category in ['pastry', 'coffee']:
|
||||
adjusted *= 1.2 # People stay indoors, buy comfort food
|
||||
|
||||
return adjusted
|
||||
|
||||
async def _load_model(self, model_id: str, model_path: str):
|
||||
"""Load model from file with improved validation and error handling"""
|
||||
|
||||
|
||||
Reference in New Issue
Block a user