New alert service

This commit is contained in:
Urtzi Alfaro
2025-12-05 20:07:01 +01:00
parent 1fe3a73549
commit 667e6e0404
393 changed files with 26002 additions and 61033 deletions

View File

@@ -1,23 +1,33 @@
# ================================================================
# services/alert_processor/app/core/config.py
# ================================================================
"""
Alert Processor Service Configuration
Configuration settings for alert processor service.
"""
import os
from pydantic import Field
from shared.config.base import BaseServiceSettings
class AlertProcessorSettings(BaseServiceSettings):
"""Alert Processor service specific settings"""
class Settings(BaseServiceSettings):
"""Application settings"""
# Service Identity
# Service info - override defaults
SERVICE_NAME: str = "alert-processor"
APP_NAME: str = "Alert Processor Service"
SERVICE_NAME: str = "alert-processor-service"
VERSION: str = "1.0.0"
DESCRIPTION: str = "Central alert and recommendation processor"
VERSION: str = "2.0.0"
# Alert processor specific settings
RABBITMQ_EXCHANGE: str = "events.exchange"
RABBITMQ_QUEUE: str = "alert_processor.queue"
REDIS_SSE_PREFIX: str = "alerts"
ORCHESTRATOR_TIMEOUT: int = 10
NOTIFICATION_TIMEOUT: int = 5
CACHE_ENABLED: bool = True
CACHE_TTL_SECONDS: int = 300
@property
def NOTIFICATION_URL(self) -> str:
"""Get notification service URL for backwards compatibility"""
return self.NOTIFICATION_SERVICE_URL
# Database configuration (secure approach - build from components)
@property
@@ -31,102 +41,11 @@ class AlertProcessorSettings(BaseServiceSettings):
# Build from components (secure approach)
user = os.getenv("ALERT_PROCESSOR_DB_USER", "alert_processor_user")
password = os.getenv("ALERT_PROCESSOR_DB_PASSWORD", "alert_processor_pass123")
host = os.getenv("ALERT_PROCESSOR_DB_HOST", "localhost")
host = os.getenv("ALERT_PROCESSOR_DB_HOST", "alert-processor-db-service")
port = os.getenv("ALERT_PROCESSOR_DB_PORT", "5432")
name = os.getenv("ALERT_PROCESSOR_DB_NAME", "alert_processor_db")
return f"postgresql+asyncpg://{user}:{password}@{host}:{port}/{name}"
# Use dedicated Redis DB for alert processing
REDIS_DB: int = int(os.getenv("ALERT_PROCESSOR_REDIS_DB", "6"))
# Alert processing configuration
BATCH_SIZE: int = int(os.getenv("ALERT_BATCH_SIZE", "10"))
PROCESSING_TIMEOUT: int = int(os.getenv("ALERT_PROCESSING_TIMEOUT", "30"))
# Deduplication settings
ALERT_DEDUPLICATION_WINDOW_MINUTES: int = int(os.getenv("ALERT_DEDUPLICATION_WINDOW_MINUTES", "15"))
RECOMMENDATION_DEDUPLICATION_WINDOW_MINUTES: int = int(os.getenv("RECOMMENDATION_DEDUPLICATION_WINDOW_MINUTES", "60"))
# Alert severity channel mappings (hardcoded for now to avoid config parsing issues)
@property
def urgent_channels(self) -> list[str]:
return ["whatsapp", "email", "push", "dashboard"]
@property
def high_channels(self) -> list[str]:
return ["whatsapp", "email", "dashboard"]
@property
def medium_channels(self) -> list[str]:
return ["email", "dashboard"]
@property
def low_channels(self) -> list[str]:
return ["dashboard"]
# ============================================================
# ENRICHMENT CONFIGURATION (NEW)
# ============================================================
# Priority scoring weights
BUSINESS_IMPACT_WEIGHT: float = float(os.getenv("BUSINESS_IMPACT_WEIGHT", "0.4"))
URGENCY_WEIGHT: float = float(os.getenv("URGENCY_WEIGHT", "0.3"))
USER_AGENCY_WEIGHT: float = float(os.getenv("USER_AGENCY_WEIGHT", "0.2"))
CONFIDENCE_WEIGHT: float = float(os.getenv("CONFIDENCE_WEIGHT", "0.1"))
# Priority thresholds
CRITICAL_THRESHOLD: int = int(os.getenv("CRITICAL_THRESHOLD", "90"))
IMPORTANT_THRESHOLD: int = int(os.getenv("IMPORTANT_THRESHOLD", "70"))
STANDARD_THRESHOLD: int = int(os.getenv("STANDARD_THRESHOLD", "50"))
# Timing intelligence
TIMING_INTELLIGENCE_ENABLED: bool = os.getenv("TIMING_INTELLIGENCE_ENABLED", "true").lower() == "true"
BATCH_LOW_PRIORITY_ALERTS: bool = os.getenv("BATCH_LOW_PRIORITY_ALERTS", "true").lower() == "true"
BUSINESS_HOURS_START: int = int(os.getenv("BUSINESS_HOURS_START", "6"))
BUSINESS_HOURS_END: int = int(os.getenv("BUSINESS_HOURS_END", "22"))
PEAK_HOURS_START: int = int(os.getenv("PEAK_HOURS_START", "7"))
PEAK_HOURS_END: int = int(os.getenv("PEAK_HOURS_END", "11"))
PEAK_HOURS_EVENING_START: int = int(os.getenv("PEAK_HOURS_EVENING_START", "17"))
PEAK_HOURS_EVENING_END: int = int(os.getenv("PEAK_HOURS_EVENING_END", "19"))
# Grouping
GROUPING_TIME_WINDOW_MINUTES: int = int(os.getenv("GROUPING_TIME_WINDOW_MINUTES", "15"))
MAX_ALERTS_PER_GROUP: int = int(os.getenv("MAX_ALERTS_PER_GROUP", "5"))
# Email digest
EMAIL_DIGEST_ENABLED: bool = os.getenv("EMAIL_DIGEST_ENABLED", "true").lower() == "true"
DIGEST_SEND_TIME: str = os.getenv("DIGEST_SEND_TIME", "18:00")
DIGEST_SEND_TIME_HOUR: int = int(os.getenv("DIGEST_SEND_TIME", "18:00").split(":")[0])
DIGEST_MIN_ALERTS: int = int(os.getenv("DIGEST_MIN_ALERTS", "5"))
# Alert grouping
ALERT_GROUPING_ENABLED: bool = os.getenv("ALERT_GROUPING_ENABLED", "true").lower() == "true"
MIN_ALERTS_FOR_GROUPING: int = int(os.getenv("MIN_ALERTS_FOR_GROUPING", "3"))
# Trend detection
TREND_DETECTION_ENABLED: bool = os.getenv("TREND_DETECTION_ENABLED", "true").lower() == "true"
TREND_LOOKBACK_DAYS: int = int(os.getenv("TREND_LOOKBACK_DAYS", "7"))
TREND_SIGNIFICANCE_THRESHOLD: float = float(os.getenv("TREND_SIGNIFICANCE_THRESHOLD", "0.15"))
# Context enrichment
ENRICHMENT_TIMEOUT_SECONDS: int = int(os.getenv("ENRICHMENT_TIMEOUT_SECONDS", "10"))
ORCHESTRATOR_CONTEXT_CACHE_TTL: int = int(os.getenv("ORCHESTRATOR_CONTEXT_CACHE_TTL", "300"))
# Peak hours (aliases for enrichment services)
EVENING_PEAK_START: int = int(os.getenv("PEAK_HOURS_EVENING_START", "17"))
EVENING_PEAK_END: int = int(os.getenv("PEAK_HOURS_EVENING_END", "19"))
# Service URLs for enrichment
ORCHESTRATOR_SERVICE_URL: str = os.getenv("ORCHESTRATOR_SERVICE_URL", "http://orchestrator-service:8000")
INVENTORY_SERVICE_URL: str = os.getenv("INVENTORY_SERVICE_URL", "http://inventory-service:8000")
PRODUCTION_SERVICE_URL: str = os.getenv("PRODUCTION_SERVICE_URL", "http://production-service:8000")
# Global settings instance
settings = AlertProcessorSettings()
def get_settings():
"""Get the global settings instance"""
return settings
settings = Settings()

View File

@@ -0,0 +1,48 @@
"""
Database connection and session management for Alert Processor Service
"""
from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker
from .config import settings
from shared.database.base import DatabaseManager
# Initialize database manager
database_manager = DatabaseManager(
database_url=settings.DATABASE_URL,
service_name=settings.SERVICE_NAME,
pool_size=settings.DB_POOL_SIZE,
max_overflow=settings.DB_MAX_OVERFLOW,
echo=settings.DEBUG
)
# Create async session factory
AsyncSessionLocal = async_sessionmaker(
database_manager.async_engine,
class_=AsyncSession,
expire_on_commit=False,
autocommit=False,
autoflush=False,
)
async def get_db() -> AsyncSession:
"""
Dependency to get database session.
Used in FastAPI endpoints via Depends(get_db).
"""
async with AsyncSessionLocal() as session:
try:
yield session
finally:
await session.close()
async def init_db():
"""Initialize database (create tables if needed)"""
await database_manager.create_all()
async def close_db():
"""Close database connections"""
await database_manager.close()