2025-10-26 19:04:51 +00:00
|
|
|
#!/usr/bin/env python3
|
|
|
|
|
"""
|
|
|
|
|
Calejo Control Adapter - Phase 1 Implementation
|
|
|
|
|
|
|
|
|
|
Core infrastructure implementation including:
|
|
|
|
|
- Database setup and connection pooling
|
|
|
|
|
- Auto-discovery of pump stations and pumps
|
|
|
|
|
- Configuration management
|
|
|
|
|
- Structured logging and audit system
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
import asyncio
|
|
|
|
|
import signal
|
|
|
|
|
import sys
|
|
|
|
|
|
|
|
|
|
from src.database.client import DatabaseClient
|
|
|
|
|
from src.core.auto_discovery import AutoDiscovery
|
|
|
|
|
from src.core.safety import SafetyLimitEnforcer
|
2025-10-27 07:32:01 +00:00
|
|
|
from src.core.emergency_stop import EmergencyStopManager
|
2025-10-27 07:00:57 +00:00
|
|
|
from src.core.optimization_manager import OptimizationPlanManager
|
2025-10-26 19:04:51 +00:00
|
|
|
from src.core.logging import setup_logging, AuditLogger
|
|
|
|
|
from config.settings import settings
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class CalejoControlAdapterPhase1:
|
|
|
|
|
"""Main application class for Phase 1 implementation."""
|
|
|
|
|
|
|
|
|
|
def __init__(self):
|
|
|
|
|
# Setup logging first
|
|
|
|
|
self.logger = setup_logging()
|
|
|
|
|
|
|
|
|
|
# Initialize core components
|
|
|
|
|
self.db_client = DatabaseClient(
|
|
|
|
|
database_url=settings.database_url,
|
|
|
|
|
min_connections=settings.db_min_connections,
|
|
|
|
|
max_connections=settings.db_max_connections
|
|
|
|
|
)
|
|
|
|
|
self.auto_discovery = AutoDiscovery(
|
|
|
|
|
db_client=self.db_client,
|
|
|
|
|
refresh_interval_minutes=settings.auto_discovery_refresh_minutes
|
|
|
|
|
)
|
2025-10-27 07:32:01 +00:00
|
|
|
self.emergency_stop_manager = EmergencyStopManager(self.db_client)
|
|
|
|
|
self.safety_enforcer = SafetyLimitEnforcer(self.db_client, self.emergency_stop_manager)
|
2025-10-27 07:00:57 +00:00
|
|
|
self.optimization_manager = OptimizationPlanManager(
|
|
|
|
|
db_client=self.db_client,
|
|
|
|
|
refresh_interval_seconds=settings.optimization_refresh_seconds
|
|
|
|
|
)
|
2025-10-26 19:04:51 +00:00
|
|
|
self.audit_logger = AuditLogger(self.db_client)
|
|
|
|
|
|
|
|
|
|
self.running = False
|
|
|
|
|
self.startup_time = None
|
|
|
|
|
|
|
|
|
|
async def start(self):
|
|
|
|
|
"""Start the Phase 1 application."""
|
|
|
|
|
self.startup_time = asyncio.get_event_loop().time()
|
|
|
|
|
|
|
|
|
|
self.logger.info(
|
|
|
|
|
"phase1_application_starting",
|
|
|
|
|
app_name=settings.app_name,
|
|
|
|
|
version=settings.app_version,
|
|
|
|
|
environment=settings.environment
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
# Connect to database
|
|
|
|
|
await self.db_client.connect()
|
|
|
|
|
|
|
|
|
|
# Initialize auto-discovery
|
|
|
|
|
if settings.auto_discovery_enabled:
|
|
|
|
|
await self.auto_discovery.discover()
|
|
|
|
|
|
|
|
|
|
# Validate discovery
|
|
|
|
|
validation = self.auto_discovery.validate_discovery()
|
|
|
|
|
if not validation['valid']:
|
|
|
|
|
self.logger.warning(
|
|
|
|
|
"auto_discovery_validation_issues",
|
|
|
|
|
issues=validation['issues']
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
# Start periodic discovery
|
|
|
|
|
asyncio.create_task(self.auto_discovery.start_periodic_discovery())
|
|
|
|
|
|
|
|
|
|
# Initialize safety framework
|
|
|
|
|
await self.safety_enforcer.load_safety_limits()
|
|
|
|
|
|
2025-10-27 07:00:57 +00:00
|
|
|
# Initialize optimization plan monitoring
|
|
|
|
|
if settings.optimization_monitoring_enabled:
|
|
|
|
|
await self.optimization_manager.start_monitoring()
|
|
|
|
|
|
2025-10-26 19:04:51 +00:00
|
|
|
# Log startup to audit trail
|
|
|
|
|
self.audit_logger.log(
|
|
|
|
|
event_type='SYSTEM_STARTUP',
|
|
|
|
|
severity='INFO',
|
|
|
|
|
user_id='system',
|
|
|
|
|
action='startup',
|
|
|
|
|
resource='system',
|
|
|
|
|
result='SUCCESS',
|
|
|
|
|
event_data={
|
|
|
|
|
'version': settings.app_version,
|
|
|
|
|
'phase': '1',
|
2025-10-27 07:00:57 +00:00
|
|
|
'components_initialized': ['database', 'auto_discovery', 'safety', 'optimization']
|
2025-10-26 19:04:51 +00:00
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
self.running = True
|
|
|
|
|
startup_duration = asyncio.get_event_loop().time() - self.startup_time
|
|
|
|
|
|
|
|
|
|
self.logger.info(
|
|
|
|
|
"phase1_application_started",
|
|
|
|
|
startup_duration_seconds=round(startup_duration, 2),
|
|
|
|
|
station_count=len(self.auto_discovery.get_stations()),
|
|
|
|
|
pump_count=len(self.auto_discovery.get_pumps()),
|
2025-10-27 07:00:57 +00:00
|
|
|
safety_limits_loaded=len(self.safety_enforcer.safety_limits_cache),
|
|
|
|
|
optimization_plans_loaded=len(self.optimization_manager.get_all_pump_plans())
|
2025-10-26 19:04:51 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
# Print status information
|
|
|
|
|
self.print_status()
|
|
|
|
|
|
|
|
|
|
# Keep application running
|
|
|
|
|
while self.running:
|
|
|
|
|
await asyncio.sleep(1)
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
self.logger.error("phase1_application_startup_failed", error=str(e))
|
|
|
|
|
await self.stop()
|
|
|
|
|
raise
|
|
|
|
|
|
|
|
|
|
async def stop(self):
|
|
|
|
|
"""Stop the application."""
|
|
|
|
|
self.logger.info("phase1_application_stopping")
|
|
|
|
|
self.running = False
|
|
|
|
|
|
|
|
|
|
# Log shutdown to audit trail
|
|
|
|
|
self.audit_logger.log(
|
|
|
|
|
event_type='SYSTEM_SHUTDOWN',
|
|
|
|
|
severity='INFO',
|
|
|
|
|
user_id='system',
|
|
|
|
|
action='shutdown',
|
|
|
|
|
resource='system',
|
|
|
|
|
result='SUCCESS'
|
|
|
|
|
)
|
|
|
|
|
|
2025-10-27 07:00:57 +00:00
|
|
|
# Stop optimization monitoring
|
|
|
|
|
await self.optimization_manager.stop_monitoring()
|
|
|
|
|
|
2025-10-26 19:04:51 +00:00
|
|
|
# Disconnect from database
|
|
|
|
|
await self.db_client.disconnect()
|
|
|
|
|
|
|
|
|
|
self.logger.info("phase1_application_stopped")
|
|
|
|
|
|
|
|
|
|
def get_status(self) -> dict:
|
|
|
|
|
"""Get application status information."""
|
|
|
|
|
return {
|
|
|
|
|
"running": self.running,
|
|
|
|
|
"app_name": settings.app_name,
|
|
|
|
|
"version": settings.app_version,
|
|
|
|
|
"environment": settings.environment,
|
|
|
|
|
"database": self.db_client.get_connection_stats(),
|
|
|
|
|
"auto_discovery": self.auto_discovery.get_discovery_status(),
|
2025-10-27 07:00:57 +00:00
|
|
|
"safety_limits_loaded": len(self.safety_enforcer.safety_limits_cache),
|
|
|
|
|
"optimization_manager": self.optimization_manager.get_status()
|
2025-10-26 19:04:51 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
def print_status(self):
|
|
|
|
|
"""Print human-readable status information."""
|
|
|
|
|
status = self.get_status()
|
|
|
|
|
|
|
|
|
|
print("\n" + "="*60)
|
|
|
|
|
print(f"Calejo Control Adapter - Phase 1 Status")
|
|
|
|
|
print("="*60)
|
|
|
|
|
print(f"Application: {status['app_name']} v{status['version']}")
|
|
|
|
|
print(f"Environment: {status['environment']}")
|
|
|
|
|
print(f"Status: {'RUNNING' if status['running'] else 'STOPPED'}")
|
|
|
|
|
print()
|
|
|
|
|
|
|
|
|
|
# Database status
|
|
|
|
|
db_stats = status['database']
|
|
|
|
|
print(f"Database:")
|
|
|
|
|
print(f" Status: {db_stats.get('pool_status', 'N/A')}")
|
|
|
|
|
print(f" Connections: {db_stats.get('min_connections', 'N/A')}-{db_stats.get('max_connections', 'N/A')}")
|
|
|
|
|
|
|
|
|
|
# Auto-discovery status
|
|
|
|
|
discovery_status = status['auto_discovery']
|
|
|
|
|
print(f"\nAuto-Discovery:")
|
|
|
|
|
print(f" Stations: {discovery_status['station_count']}")
|
|
|
|
|
print(f" Pumps: {discovery_status['pump_count']}")
|
|
|
|
|
print(f" Last Discovery: {discovery_status['last_discovery'] or 'Never'}")
|
|
|
|
|
|
|
|
|
|
# Safety framework status
|
|
|
|
|
print(f"\nSafety Framework:")
|
|
|
|
|
print(f" Limits Loaded: {status['safety_limits_loaded']}")
|
|
|
|
|
|
2025-10-27 07:00:57 +00:00
|
|
|
# Optimization manager status
|
|
|
|
|
opt_status = status['optimization_manager']
|
|
|
|
|
print(f"\nOptimization Manager:")
|
|
|
|
|
print(f" Status: {'RUNNING' if opt_status['running'] else 'STOPPED'}")
|
|
|
|
|
print(f" Pump Plans: {opt_status['active_pump_plans']}")
|
|
|
|
|
print(f" Generic Plans: {opt_status['active_generic_plans']}")
|
|
|
|
|
print(f" Last Refresh: {opt_status['last_refresh'] or 'Never'}")
|
|
|
|
|
|
2025-10-26 19:04:51 +00:00
|
|
|
print("="*60)
|
|
|
|
|
print("\nPress Ctrl+C to stop the application\n")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async def main():
|
|
|
|
|
"""Main application entry point for Phase 1."""
|
|
|
|
|
|
|
|
|
|
app = CalejoControlAdapterPhase1()
|
|
|
|
|
|
|
|
|
|
# Setup signal handlers for graceful shutdown
|
|
|
|
|
def signal_handler(signum, frame):
|
|
|
|
|
logger = app.logger
|
|
|
|
|
logger.info("signal_received", signal=signum)
|
|
|
|
|
asyncio.create_task(app.stop())
|
|
|
|
|
|
|
|
|
|
signal.signal(signal.SIGINT, signal_handler)
|
|
|
|
|
signal.signal(signal.SIGTERM, signal_handler)
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
await app.start()
|
|
|
|
|
except KeyboardInterrupt:
|
|
|
|
|
await app.stop()
|
|
|
|
|
except Exception as e:
|
|
|
|
|
logger = app.logger
|
|
|
|
|
logger.error("phase1_application_failed", error=str(e))
|
|
|
|
|
sys.exit(1)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
|
asyncio.run(main())
|