# Forecast Engine MCP Service """ Enterprise Time-Series Forecasting Engine Features: - Linear regression-based forecasting - Moving average analysis - Confidence interval calculation - Trend detection - Seasonal pattern recognition Usage: from mcp.forecast_engine import ForecastEngine engine = ForecastEngine() result = engine.forecast(data, periods=7) """ import numpy as np from typing import Dict, List, Any, Optional, Tuple from datetime import datetime, timedelta from dataclasses import dataclass import json @dataclass class ForecastResult: """Result from forecast engine""" forecast: List[Dict[str, Any]] lower_bound: List[float] upper_bound: List[float] trend: str trend_slope: float confidence_score: float seasonality: Optional[str] insights: List[str] class ForecastEngine: """ Enterprise Forecast Engine using statistical methods. Supports: - Linear trend forecasting - Moving average forecasting - Exponential smoothing - Confidence intervals """ def __init__(self): self.min_data_points = 2 self.default_confidence = 0.95 def forecast( self, data: List[Dict[str, Any]], date_column: str = "date", value_column: str = "value", periods: int = 7, confidence: float = 0.95 ) -> ForecastResult: """ Generate forecast for time-series data. Args: data: List of dicts with date and value columns date_column: Name of date column value_column: Name of value column periods: Number of periods to forecast confidence: Confidence level (0-1) Returns: ForecastResult with predictions and analysis """ if len(data) < self.min_data_points: # Attempt to proceed if we have at least 1 point (for basic plotting) if len(data) == 0: return self._empty_result("Insufficient data for forecasting") # Extract values values = [] dates = [] for item in data: try: val = float(item.get(value_column, 0)) values.append(val) dates.append(item.get(date_column, "")) except (ValueError, TypeError): continue if len(values) == 0: return self._empty_result("No valid numeric values found") # Calculate trend trend, slope = self._calculate_trend(values) # Generate forecast using linear regression # If too few points, assume flat or simple average if len(values) < 2: forecast_values = [values[0]] * periods else: forecast_values = self._linear_forecast(values, periods) # Calculate confidence intervals lower, upper = self._calculate_confidence_intervals( values, forecast_values, confidence ) # Detect seasonality seasonality = self._detect_seasonality(values) # Generate insights insights = self._generate_insights(values, forecast_values, trend, slope) # Calculate confidence score conf_score = self._calculate_confidence_score(values, len(data)) # Build forecast with dates forecast_data = self._build_forecast_output( dates, values, forecast_values, lower, upper, periods ) return ForecastResult( forecast=forecast_data, lower_bound=lower, upper_bound=upper, trend=trend, trend_slope=slope, confidence_score=conf_score, seasonality=seasonality, insights=insights ) def _calculate_trend(self, values: List[float]) -> Tuple[str, float]: """Calculate trend direction and slope.""" n = len(values) if n < 2: return "stable", 0.0 x = np.arange(n) y = np.array(values) # Linear regression slope = (n * np.sum(x * y) - np.sum(x) * np.sum(y)) / \ (n * np.sum(x**2) - np.sum(x)**2) # Determine trend avg_value = np.mean(values) relative_slope = slope / avg_value if avg_value != 0 else 0 if relative_slope > 0.02: trend = "strongly_increasing" elif relative_slope > 0.005: trend = "increasing" elif relative_slope < -0.02: trend = "strongly_decreasing" elif relative_slope < -0.005: trend = "decreasing" else: trend = "stable" return trend, float(slope) def _linear_forecast(self, values: List[float], periods: int) -> List[float]: """Generate forecast using linear regression.""" n = len(values) x = np.arange(n) y = np.array(values) # Calculate slope and intercept slope = (n * np.sum(x * y) - np.sum(x) * np.sum(y)) / \ (n * np.sum(x**2) - np.sum(x)**2) intercept = (np.sum(y) - slope * np.sum(x)) / n # Generate future values forecast = [] for i in range(periods): pred = intercept + slope * (n + i) # Apply moving average smoothing if len(values) >= 3: ma = np.mean(values[-3:]) pred = pred * 0.7 + ma * 0.3 forecast.append(max(0, pred)) # Ensure non-negative return forecast def _calculate_confidence_intervals( self, historical: List[float], forecast: List[float], confidence: float ) -> Tuple[List[float], List[float]]: """Calculate confidence intervals for forecast.""" # Calculate historical standard deviation std = np.std(historical) if len(historical) > 1 else 0 # Z-score for confidence level z_scores = {0.90: 1.645, 0.95: 1.96, 0.99: 2.576} z = z_scores.get(confidence, 1.96) # Expand intervals for future periods lower = [] upper = [] for i, val in enumerate(forecast): expansion = 1 + (i * 0.1) # Wider intervals for further predictions margin = z * std * expansion lower.append(max(0, val - margin)) upper.append(val + margin) return lower, upper def _detect_seasonality(self, values: List[float]) -> Optional[str]: """Detect seasonality patterns.""" if len(values) < 7: return None # Check for weekly pattern (7-day cycle) if len(values) >= 14: weekly_diff = [] for i in range(7, len(values)): diff = abs(values[i] - values[i-7]) weekly_diff.append(diff) avg_weekly_var = np.mean(weekly_diff) overall_var = np.std(values) if avg_weekly_var < overall_var * 0.5: return "weekly" # Check for monthly pattern if len(values) >= 60: return "monthly" return None def _generate_insights( self, historical: List[float], forecast: List[float], trend: str, slope: float ) -> List[str]: """Generate human-readable insights.""" insights = [] # Trend insight trend_labels = { "strongly_increasing": "Revenue is growing rapidly", "increasing": "Revenue shows steady growth", "stable": "Revenue is relatively stable", "decreasing": "Revenue is declining", "strongly_decreasing": "Revenue is declining significantly" } insights.append(trend_labels.get(trend, "Trend analysis complete")) # Forecast summary if forecast: avg_forecast = np.mean(forecast) avg_historical = np.mean(historical) change_pct = ((avg_forecast - avg_historical) / avg_historical * 100) \ if avg_historical != 0 else 0 if change_pct > 5: insights.append(f"Projected {change_pct:.1f}% increase in coming period") elif change_pct < -5: insights.append(f"Projected {abs(change_pct):.1f}% decrease in coming period") else: insights.append("Forecast shows minimal change from current levels") # Volatility insight if len(historical) > 3: cv = np.std(historical) / np.mean(historical) if np.mean(historical) != 0 else 0 if cv > 0.3: insights.append("⚠️ High volatility detected - predictions may vary") elif cv < 0.1: insights.append("✓ Low volatility - high prediction confidence") return insights def _calculate_confidence_score(self, values: List[float], n_points: int) -> float: """Calculate overall confidence score (0-100).""" # Base score on data points data_score = min(n_points / 30 * 50, 50) # Max 50 from data quantity # Score based on stability if len(values) > 1: cv = np.std(values) / np.mean(values) if np.mean(values) != 0 else 1 stability_score = max(0, 50 * (1 - cv)) else: stability_score = 0 return min(round(data_score + stability_score, 1), 100) def _build_forecast_output( self, dates: List[str], historical: List[float], forecast: List[float], lower: List[float], upper: List[float], periods: int ) -> List[Dict[str, Any]]: """Build structured forecast output.""" output = [] # Add historical data for i, (date, value) in enumerate(zip(dates, historical)): output.append({ "period": i + 1, "date": date, "value": round(value, 2), "type": "historical" }) # Add forecast data last_date = dates[-1] if dates else "" for i, (val, low, up) in enumerate(zip(forecast, lower, upper)): output.append({ "period": len(historical) + i + 1, "date": f"Forecast +{i+1}", "value": round(val, 2), "lower": round(low, 2), "upper": round(up, 2), "type": "forecast" }) return output def _empty_result(self, message: str) -> ForecastResult: """Return empty result with message.""" return ForecastResult( forecast=[], lower_bound=[], upper_bound=[], trend="unknown", trend_slope=0.0, confidence_score=0.0, seasonality=None, insights=[message] ) def forecast_from_dataframe(df, date_col: str, value_col: str, periods: int = 7) -> Dict: """ Convenience function to forecast from a pandas DataFrame. Args: df: pandas DataFrame date_col: Name of date column value_col: Name of value column periods: Number of periods to forecast Returns: Dict with forecast results """ try: # Convert DataFrame to list of dicts data = df[[date_col, value_col]].rename( columns={date_col: "date", value_col: "value"} ).to_dict('records') engine = ForecastEngine() result = engine.forecast(data, periods=periods) return { "success": True, "forecast": result.forecast, "lower_bound": result.lower_bound, "upper_bound": result.upper_bound, "trend": result.trend, "confidence": f"{result.confidence_score}%", "insights": result.insights } except Exception as e: return { "success": False, "error": str(e), "forecast": [], "insights": [f"Forecast error: {str(e)}"] } # Quick test if __name__ == "__main__": test_data = [ {"date": "2024-01-01", "value": 10000}, {"date": "2024-01-02", "value": 10500}, {"date": "2024-01-03", "value": 10200}, {"date": "2024-01-04", "value": 11000}, {"date": "2024-01-05", "value": 11200}, {"date": "2024-01-06", "value": 10800}, {"date": "2024-01-07", "value": 11500}, ] engine = ForecastEngine() result = engine.forecast(test_data, periods=7) print("Forecast Results:") print(f"Trend: {result.trend}") print(f"Confidence: {result.confidence_score}%") print(f"Insights: {result.insights}") print(f"Forecast: {result.forecast[-3:]}")