Add standalone test script for mock services without Docker dependency
This commit is contained in:
parent
d6635806f3
commit
7372c5a161
|
|
@ -0,0 +1,379 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
Standalone test script for mock SCADA and optimizer services
|
||||
This script can test the services without requiring Docker
|
||||
"""
|
||||
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
import requests
|
||||
import json
|
||||
from datetime import datetime
|
||||
|
||||
def run_command(cmd, check=True):
|
||||
"""Run a shell command and return output"""
|
||||
try:
|
||||
result = subprocess.run(cmd, shell=True, capture_output=True, text=True, check=check)
|
||||
return result.stdout, result.stderr, result.returncode
|
||||
except subprocess.CalledProcessError as e:
|
||||
return e.stdout, e.stderr, e.returncode
|
||||
|
||||
def check_python_dependencies():
|
||||
"""Check if required Python packages are installed"""
|
||||
required_packages = ['flask', 'requests']
|
||||
missing = []
|
||||
|
||||
for package in required_packages:
|
||||
try:
|
||||
__import__(package)
|
||||
except ImportError:
|
||||
missing.append(package)
|
||||
|
||||
if missing:
|
||||
print(f"❌ Missing required packages: {', '.join(missing)}")
|
||||
print("Install with: pip install flask requests")
|
||||
return False
|
||||
|
||||
print("✅ All required Python packages are installed")
|
||||
return True
|
||||
|
||||
def start_mock_services():
|
||||
"""Start the mock services in background"""
|
||||
print("🚀 Starting mock services...")
|
||||
|
||||
# Start SCADA service
|
||||
scada_cmd = "cd tests/mock_services && python mock_scada_server.py > /tmp/scada.log 2>&1 &"
|
||||
stdout, stderr, code = run_command(scada_cmd)
|
||||
|
||||
# Start Optimizer service
|
||||
optimizer_cmd = "cd tests/mock_services && python mock_optimizer_server.py > /tmp/optimizer.log 2>&1 &"
|
||||
stdout, stderr, code = run_command(optimizer_cmd)
|
||||
|
||||
print("✅ Mock services started in background")
|
||||
print(" SCADA logs: /tmp/scada.log")
|
||||
print(" Optimizer logs: /tmp/optimizer.log")
|
||||
|
||||
def wait_for_services():
|
||||
"""Wait for services to be ready"""
|
||||
print("⏳ Waiting for services to be ready...")
|
||||
|
||||
max_wait = 30
|
||||
start_time = time.time()
|
||||
|
||||
while time.time() - start_time < max_wait:
|
||||
try:
|
||||
scada_ready = requests.get("http://localhost:8081/health", timeout=2).status_code == 200
|
||||
optimizer_ready = requests.get("http://localhost:8082/health", timeout=2).status_code == 200
|
||||
|
||||
if scada_ready and optimizer_ready:
|
||||
print("✅ All services are ready!")
|
||||
return True
|
||||
except:
|
||||
pass
|
||||
|
||||
print(f" Waiting... ({int(time.time() - start_time)}/{max_wait} seconds)")
|
||||
time.sleep(2)
|
||||
|
||||
print("❌ Services not ready within timeout period")
|
||||
return False
|
||||
|
||||
def test_scada_service():
|
||||
"""Test SCADA service functionality"""
|
||||
print("\n📊 Testing SCADA Service...")
|
||||
|
||||
tests_passed = 0
|
||||
total_tests = 0
|
||||
|
||||
try:
|
||||
# Test health endpoint
|
||||
total_tests += 1
|
||||
response = requests.get("http://localhost:8081/health")
|
||||
if response.status_code == 200 and response.json().get("status") == "healthy":
|
||||
print(" ✅ Health check passed")
|
||||
tests_passed += 1
|
||||
else:
|
||||
print(" ❌ Health check failed")
|
||||
|
||||
# Test data endpoint
|
||||
total_tests += 1
|
||||
response = requests.get("http://localhost:8081/api/v1/data")
|
||||
if response.status_code == 200:
|
||||
data = response.json()
|
||||
if "data" in data and "equipment" in data:
|
||||
print(" ✅ Data retrieval passed")
|
||||
tests_passed += 1
|
||||
else:
|
||||
print(" ❌ Data structure invalid")
|
||||
else:
|
||||
print(" ❌ Data endpoint failed")
|
||||
|
||||
# Test specific data tag
|
||||
total_tests += 1
|
||||
response = requests.get("http://localhost:8081/api/v1/data/temperature")
|
||||
if response.status_code == 200:
|
||||
data = response.json()
|
||||
if "value" in data and "unit" in data:
|
||||
print(" ✅ Specific data tag passed")
|
||||
tests_passed += 1
|
||||
else:
|
||||
print(" ❌ Specific data structure invalid")
|
||||
else:
|
||||
print(" ❌ Specific data endpoint failed")
|
||||
|
||||
# Test equipment control
|
||||
total_tests += 1
|
||||
response = requests.post(
|
||||
"http://localhost:8081/api/v1/control/pump_1",
|
||||
json={"command": "START"}
|
||||
)
|
||||
if response.status_code == 200:
|
||||
data = response.json()
|
||||
if "current_status" in data and data["current_status"] == "START":
|
||||
print(" ✅ Equipment control passed")
|
||||
tests_passed += 1
|
||||
else:
|
||||
print(" ❌ Equipment control response invalid")
|
||||
else:
|
||||
print(" ❌ Equipment control failed")
|
||||
|
||||
# Test alarms
|
||||
total_tests += 1
|
||||
response = requests.get("http://localhost:8081/api/v1/alarms")
|
||||
if response.status_code == 200:
|
||||
data = response.json()
|
||||
if "alarms" in data:
|
||||
print(" ✅ Alarms endpoint passed")
|
||||
tests_passed += 1
|
||||
else:
|
||||
print(" ❌ Alarms structure invalid")
|
||||
else:
|
||||
print(" ❌ Alarms endpoint failed")
|
||||
|
||||
except Exception as e:
|
||||
print(f" ❌ SCADA test error: {e}")
|
||||
|
||||
print(f" 📈 SCADA tests: {tests_passed}/{total_tests} passed")
|
||||
return tests_passed, total_tests
|
||||
|
||||
def test_optimizer_service():
|
||||
"""Test optimizer service functionality"""
|
||||
print("\n🧠 Testing Optimizer Service...")
|
||||
|
||||
tests_passed = 0
|
||||
total_tests = 0
|
||||
|
||||
try:
|
||||
# Test health endpoint
|
||||
total_tests += 1
|
||||
response = requests.get("http://localhost:8082/health")
|
||||
if response.status_code == 200 and response.json().get("status") == "healthy":
|
||||
print(" ✅ Health check passed")
|
||||
tests_passed += 1
|
||||
else:
|
||||
print(" ❌ Health check failed")
|
||||
|
||||
# Test models endpoint
|
||||
total_tests += 1
|
||||
response = requests.get("http://localhost:8082/api/v1/models")
|
||||
if response.status_code == 200:
|
||||
data = response.json()
|
||||
if "models" in data and "energy_optimization" in data["models"]:
|
||||
print(" ✅ Models endpoint passed")
|
||||
tests_passed += 1
|
||||
else:
|
||||
print(" ❌ Models structure invalid")
|
||||
else:
|
||||
print(" ❌ Models endpoint failed")
|
||||
|
||||
# Test energy optimization
|
||||
total_tests += 1
|
||||
response = requests.post(
|
||||
"http://localhost:8082/api/v1/optimize/energy_optimization",
|
||||
json={"power_load": 450, "time_of_day": 14, "production_rate": 95}
|
||||
)
|
||||
if response.status_code == 200:
|
||||
data = response.json()
|
||||
if "result" in data and "optimization_id" in data:
|
||||
print(" ✅ Energy optimization passed")
|
||||
tests_passed += 1
|
||||
else:
|
||||
print(" ❌ Optimization response invalid")
|
||||
else:
|
||||
print(" ❌ Energy optimization failed")
|
||||
|
||||
# Test forecast
|
||||
total_tests += 1
|
||||
response = requests.post(
|
||||
"http://localhost:8082/api/v1/forecast",
|
||||
json={"hours": 12}
|
||||
)
|
||||
if response.status_code == 200:
|
||||
data = response.json()
|
||||
if "forecast" in data and len(data["forecast"]) == 12:
|
||||
print(" ✅ Forecast passed")
|
||||
tests_passed += 1
|
||||
else:
|
||||
print(" ❌ Forecast structure invalid")
|
||||
else:
|
||||
print(" ❌ Forecast failed")
|
||||
|
||||
# Test history
|
||||
total_tests += 1
|
||||
response = requests.get("http://localhost:8082/api/v1/history")
|
||||
if response.status_code == 200:
|
||||
data = response.json()
|
||||
if "history" in data and "total_optimizations" in data:
|
||||
print(" ✅ History endpoint passed")
|
||||
tests_passed += 1
|
||||
else:
|
||||
print(" ❌ History structure invalid")
|
||||
else:
|
||||
print(" ❌ History endpoint failed")
|
||||
|
||||
except Exception as e:
|
||||
print(f" ❌ Optimizer test error: {e}")
|
||||
|
||||
print(f" 📈 Optimizer tests: {tests_passed}/{total_tests} passed")
|
||||
return tests_passed, total_tests
|
||||
|
||||
def test_end_to_end_workflow():
|
||||
"""Test end-to-end workflow"""
|
||||
print("\n🔄 Testing End-to-End Workflow...")
|
||||
|
||||
tests_passed = 0
|
||||
total_tests = 0
|
||||
|
||||
try:
|
||||
# Get SCADA data
|
||||
total_tests += 1
|
||||
scada_response = requests.get("http://localhost:8081/api/v1/data")
|
||||
if scada_response.status_code == 200:
|
||||
scada_data = scada_response.json()
|
||||
power_value = scada_data["data"]["power"]["value"]
|
||||
print(" ✅ SCADA data retrieved")
|
||||
tests_passed += 1
|
||||
else:
|
||||
print(" ❌ SCADA data retrieval failed")
|
||||
return tests_passed, total_tests
|
||||
|
||||
# Run optimization based on SCADA data
|
||||
total_tests += 1
|
||||
opt_response = requests.post(
|
||||
"http://localhost:8082/api/v1/optimize/energy_optimization",
|
||||
json={
|
||||
"power_load": power_value,
|
||||
"time_of_day": datetime.now().hour,
|
||||
"production_rate": 95
|
||||
}
|
||||
)
|
||||
if opt_response.status_code == 200:
|
||||
opt_data = opt_response.json()
|
||||
if "result" in opt_data and "recommended_actions" in opt_data["result"]:
|
||||
print(" ✅ Optimization based on SCADA data passed")
|
||||
tests_passed += 1
|
||||
else:
|
||||
print(" ❌ Optimization response invalid")
|
||||
else:
|
||||
print(" ❌ Optimization failed")
|
||||
|
||||
# Test control based on optimization
|
||||
total_tests += 1
|
||||
control_response = requests.post(
|
||||
"http://localhost:8081/api/v1/control/compressor",
|
||||
json={"command": "START"}
|
||||
)
|
||||
if control_response.status_code == 200:
|
||||
control_data = control_response.json()
|
||||
if "current_status" in control_data:
|
||||
print(" ✅ Control based on workflow passed")
|
||||
tests_passed += 1
|
||||
else:
|
||||
print(" ❌ Control response invalid")
|
||||
else:
|
||||
print(" ❌ Control failed")
|
||||
|
||||
except Exception as e:
|
||||
print(f" ❌ End-to-end test error: {e}")
|
||||
|
||||
print(f" 📈 End-to-end tests: {tests_passed}/{total_tests} passed")
|
||||
return tests_passed, total_tests
|
||||
|
||||
def stop_services():
|
||||
"""Stop the mock services"""
|
||||
print("\n🛑 Stopping mock services...")
|
||||
|
||||
# Find and kill the processes
|
||||
run_command("pkill -f 'python mock_scada_server.py'", check=False)
|
||||
run_command("pkill -f 'python mock_optimizer_server.py'", check=False)
|
||||
|
||||
print("✅ Mock services stopped")
|
||||
|
||||
def main():
|
||||
"""Main test runner"""
|
||||
print("🧪 Standalone Mock Services Test Runner")
|
||||
print("=" * 50)
|
||||
|
||||
# Check dependencies
|
||||
if not check_python_dependencies():
|
||||
sys.exit(1)
|
||||
|
||||
# Create mock services directory if needed
|
||||
import os
|
||||
os.makedirs("tests/mock_services", exist_ok=True)
|
||||
|
||||
# Check if mock service files exist
|
||||
if not os.path.exists("tests/mock_services/mock_scada_server.py"):
|
||||
print("❌ Mock service files not found. Run setup script first:")
|
||||
print(" ./scripts/setup-test-environment.sh")
|
||||
sys.exit(1)
|
||||
|
||||
# Start services
|
||||
start_mock_services()
|
||||
|
||||
# Wait for services
|
||||
if not wait_for_services():
|
||||
stop_services()
|
||||
sys.exit(1)
|
||||
|
||||
# Run tests
|
||||
total_passed = 0
|
||||
total_tests = 0
|
||||
|
||||
# Test SCADA
|
||||
passed, tests = test_scada_service()
|
||||
total_passed += passed
|
||||
total_tests += tests
|
||||
|
||||
# Test Optimizer
|
||||
passed, tests = test_optimizer_service()
|
||||
total_passed += passed
|
||||
total_tests += tests
|
||||
|
||||
# Test End-to-End
|
||||
passed, tests = test_end_to_end_workflow()
|
||||
total_passed += passed
|
||||
total_tests += tests
|
||||
|
||||
# Stop services
|
||||
stop_services()
|
||||
|
||||
# Print summary
|
||||
print("\n" + "=" * 50)
|
||||
print("📊 TEST SUMMARY")
|
||||
print("=" * 50)
|
||||
print(f"Total Tests: {total_tests}")
|
||||
print(f"Tests Passed: {total_passed}")
|
||||
print(f"Tests Failed: {total_tests - total_passed}")
|
||||
print(f"Success Rate: {(total_passed/total_tests)*100:.1f}%")
|
||||
|
||||
if total_passed == total_tests:
|
||||
print("\n🎉 ALL TESTS PASSED!")
|
||||
print("Mock services are working correctly!")
|
||||
else:
|
||||
print(f"\n❌ {total_tests - total_passed} TESTS FAILED")
|
||||
print("Check the logs above for details")
|
||||
sys.exit(1)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
|
@ -0,0 +1,280 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
Mock Optimizer Server for testing
|
||||
Simulates optimization service for industrial processes
|
||||
"""
|
||||
|
||||
import json
|
||||
import random
|
||||
import time
|
||||
from datetime import datetime, timedelta
|
||||
from flask import Flask, jsonify, request
|
||||
|
||||
app = Flask(__name__)
|
||||
|
||||
# Mock optimization models
|
||||
optimization_models = {
|
||||
"energy_optimization": {
|
||||
"name": "Energy Consumption Optimizer",
|
||||
"description": "Optimizes energy usage while maintaining production targets",
|
||||
"parameters": ["power_load", "time_of_day", "production_rate"],
|
||||
"outputs": ["optimal_power_setpoint", "recommended_actions", "estimated_savings", "confidence"]
|
||||
},
|
||||
"production_optimization": {
|
||||
"name": "Production Efficiency Optimizer",
|
||||
"description": "Maximizes production efficiency and throughput",
|
||||
"parameters": ["raw_material_quality", "machine_utilization", "operator_skill"],
|
||||
"outputs": ["optimal_production_rate", "efficiency_gain", "recommended_adjustments"]
|
||||
},
|
||||
"cost_optimization": {
|
||||
"name": "Cost Reduction Optimizer",
|
||||
"description": "Minimizes operational costs while maintaining quality",
|
||||
"parameters": ["energy_cost", "labor_cost", "maintenance_cost"],
|
||||
"outputs": ["optimal_cost_structure", "cost_reduction", "implementation_plan"]
|
||||
}
|
||||
}
|
||||
|
||||
# Optimization history
|
||||
optimization_history = []
|
||||
|
||||
def generate_optimization_id():
|
||||
"""Generate unique optimization ID"""
|
||||
return f"OPT_{datetime.now().strftime('%Y%m%d_%H%M%S')}_{random.randint(1000, 9999)}"
|
||||
|
||||
def run_energy_optimization(data):
|
||||
"""Run energy optimization"""
|
||||
power_load = data.get("power_load", 450.0)
|
||||
time_of_day = data.get("time_of_day", 12)
|
||||
production_rate = data.get("production_rate", 95.0)
|
||||
|
||||
# Mock optimization logic
|
||||
if time_of_day >= 6 and time_of_day <= 18:
|
||||
# Daytime optimization
|
||||
optimal_power = power_load * 0.85 # Reduce power during peak hours
|
||||
savings = power_load * 0.15 * 0.12 # 15% reduction at $0.12/kWh
|
||||
else:
|
||||
# Nighttime optimization
|
||||
optimal_power = power_load * 0.95 # Slight reduction at night
|
||||
savings = power_load * 0.05 * 0.08 # 5% reduction at $0.08/kWh
|
||||
|
||||
# Adjust based on production rate
|
||||
if production_rate < 80:
|
||||
optimal_power *= 0.9
|
||||
savings *= 1.1
|
||||
elif production_rate > 95:
|
||||
optimal_power *= 1.05
|
||||
savings *= 0.9
|
||||
|
||||
return {
|
||||
"optimal_power_setpoint": round(optimal_power, 2),
|
||||
"recommended_actions": [
|
||||
"Adjust compressor speed to 85%",
|
||||
"Optimize pump sequencing",
|
||||
"Implement variable frequency drive"
|
||||
],
|
||||
"estimated_savings": round(savings, 2),
|
||||
"confidence": random.uniform(0.85, 0.95)
|
||||
}
|
||||
|
||||
def run_production_optimization(data):
|
||||
"""Run production optimization"""
|
||||
raw_material_quality = data.get("raw_material_quality", 85.0)
|
||||
machine_utilization = data.get("machine_utilization", 92.0)
|
||||
operator_skill = data.get("operator_skill", 88.0)
|
||||
|
||||
# Mock optimization logic
|
||||
base_rate = 100.0
|
||||
|
||||
# Adjust based on factors
|
||||
quality_factor = raw_material_quality / 100.0
|
||||
utilization_factor = machine_utilization / 100.0
|
||||
skill_factor = operator_skill / 100.0
|
||||
|
||||
optimal_rate = base_rate * quality_factor * utilization_factor * skill_factor
|
||||
efficiency_gain = (optimal_rate - base_rate) / base_rate * 100
|
||||
|
||||
return {
|
||||
"optimal_production_rate": round(optimal_rate, 2),
|
||||
"efficiency_gain": round(efficiency_gain, 2),
|
||||
"recommended_adjustments": [
|
||||
"Increase raw material inspection frequency",
|
||||
"Optimize machine maintenance schedule",
|
||||
"Provide operator training on efficiency techniques"
|
||||
]
|
||||
}
|
||||
|
||||
def run_cost_optimization(data):
|
||||
"""Run cost optimization"""
|
||||
energy_cost = data.get("energy_cost", 55.0)
|
||||
labor_cost = data.get("labor_cost", 30.0)
|
||||
maintenance_cost = data.get("maintenance_cost", 15.0)
|
||||
|
||||
total_cost = energy_cost + labor_cost + maintenance_cost
|
||||
|
||||
# Mock optimization logic
|
||||
optimal_energy = energy_cost * 0.9 # 10% reduction
|
||||
optimal_labor = labor_cost * 0.95 # 5% reduction
|
||||
optimal_maintenance = maintenance_cost * 1.05 # 5% increase for better maintenance
|
||||
|
||||
optimal_total = optimal_energy + optimal_labor + optimal_maintenance
|
||||
cost_reduction = total_cost - optimal_total
|
||||
|
||||
return {
|
||||
"optimal_cost_structure": {
|
||||
"energy": round(optimal_energy, 2),
|
||||
"labor": round(optimal_labor, 2),
|
||||
"maintenance": round(optimal_maintenance, 2),
|
||||
"total": round(optimal_total, 2)
|
||||
},
|
||||
"cost_reduction": round(cost_reduction, 2),
|
||||
"implementation_plan": [
|
||||
"Implement energy monitoring system",
|
||||
"Optimize workforce scheduling",
|
||||
"Increase preventive maintenance frequency"
|
||||
]
|
||||
}
|
||||
|
||||
def generate_forecast(hours=24):
|
||||
"""Generate forecast data"""
|
||||
forecast = []
|
||||
base_time = datetime.now()
|
||||
|
||||
for i in range(hours):
|
||||
timestamp = base_time + timedelta(hours=i)
|
||||
hour = timestamp.hour
|
||||
|
||||
# Generate realistic forecast data
|
||||
if hour >= 6 and hour <= 18:
|
||||
# Daytime values
|
||||
energy_consumption = random.uniform(450, 550)
|
||||
production_rate = random.uniform(95, 105)
|
||||
efficiency = random.uniform(85, 92)
|
||||
cost = random.uniform(50, 60)
|
||||
else:
|
||||
# Nighttime values
|
||||
energy_consumption = random.uniform(350, 450)
|
||||
production_rate = random.uniform(80, 95)
|
||||
efficiency = random.uniform(78, 85)
|
||||
cost = random.uniform(40, 50)
|
||||
|
||||
forecast.append({
|
||||
"timestamp": timestamp.isoformat(),
|
||||
"energy_consumption": round(energy_consumption, 2),
|
||||
"production_rate": round(production_rate, 2),
|
||||
"efficiency": round(efficiency, 2),
|
||||
"cost": round(cost, 2)
|
||||
})
|
||||
|
||||
return forecast
|
||||
|
||||
@app.route('/health', methods=['GET'])
|
||||
def health():
|
||||
"""Health check endpoint"""
|
||||
return jsonify({
|
||||
"status": "healthy",
|
||||
"service": "mock-optimizer",
|
||||
"timestamp": datetime.now().isoformat(),
|
||||
"models_available": len(optimization_models)
|
||||
})
|
||||
|
||||
@app.route('/api/v1/models', methods=['GET'])
|
||||
def get_models():
|
||||
"""Get available optimization models"""
|
||||
return jsonify({
|
||||
"models": optimization_models,
|
||||
"timestamp": datetime.now().isoformat()
|
||||
})
|
||||
|
||||
@app.route('/api/v1/optimize/<model_name>', methods=['POST'])
|
||||
def run_optimization(model_name):
|
||||
"""Run optimization with specified model"""
|
||||
if model_name not in optimization_models:
|
||||
return jsonify({"error": f"Model '{model_name}' not found"}), 404
|
||||
|
||||
data = request.json
|
||||
if not data:
|
||||
return jsonify({"error": "No data provided"}), 400
|
||||
|
||||
start_time = time.time()
|
||||
|
||||
# Run appropriate optimization
|
||||
if model_name == "energy_optimization":
|
||||
result = run_energy_optimization(data)
|
||||
elif model_name == "production_optimization":
|
||||
result = run_production_optimization(data)
|
||||
elif model_name == "cost_optimization":
|
||||
result = run_cost_optimization(data)
|
||||
else:
|
||||
return jsonify({"error": "Model not implemented"}), 501
|
||||
|
||||
processing_time = round(time.time() - start_time, 3)
|
||||
optimization_id = generate_optimization_id()
|
||||
|
||||
# Add to history
|
||||
optimization_history.append({
|
||||
"optimization_id": optimization_id,
|
||||
"model": model_name,
|
||||
"timestamp": datetime.now().isoformat(),
|
||||
"processing_time": processing_time
|
||||
})
|
||||
|
||||
return jsonify({
|
||||
"optimization_id": optimization_id,
|
||||
"model": model_name,
|
||||
"result": result,
|
||||
"processing_time": processing_time,
|
||||
"timestamp": datetime.now().isoformat()
|
||||
})
|
||||
|
||||
@app.route('/api/v1/history', methods=['GET'])
|
||||
def get_history():
|
||||
"""Get optimization history"""
|
||||
return jsonify({
|
||||
"history": optimization_history[-10:], # Last 10 optimizations
|
||||
"total_optimizations": len(optimization_history),
|
||||
"timestamp": datetime.now().isoformat()
|
||||
})
|
||||
|
||||
@app.route('/api/v1/forecast', methods=['POST'])
|
||||
def generate_optimization_forecast():
|
||||
"""Generate optimization forecast"""
|
||||
data = request.json or {}
|
||||
hours = data.get("hours", 24)
|
||||
|
||||
if hours > 168: # Max 1 week
|
||||
return jsonify({"error": "Forecast horizon too long. Maximum 168 hours (1 week)"}), 400
|
||||
|
||||
forecast = generate_forecast(hours)
|
||||
|
||||
return jsonify({
|
||||
"forecast": forecast,
|
||||
"generated_at": datetime.now().isoformat(),
|
||||
"horizon_hours": hours
|
||||
})
|
||||
|
||||
@app.route('/api/v1/status/<optimization_id>', methods=['GET'])
|
||||
def get_optimization_status(optimization_id):
|
||||
"""Get status of specific optimization"""
|
||||
for opt in optimization_history:
|
||||
if opt["optimization_id"] == optimization_id:
|
||||
return jsonify({
|
||||
"optimization_id": optimization_id,
|
||||
"status": "completed",
|
||||
"model": opt["model"],
|
||||
"completed_at": opt["timestamp"],
|
||||
"processing_time": opt["processing_time"]
|
||||
})
|
||||
|
||||
return jsonify({"error": f"Optimization '{optimization_id}' not found"}), 404
|
||||
|
||||
if __name__ == '__main__':
|
||||
print("🧠 Starting Mock Optimizer Server on port 8082...")
|
||||
print("📊 Available endpoints:")
|
||||
print(" GET /health")
|
||||
print(" GET /api/v1/models")
|
||||
print(" POST /api/v1/optimize/<model_name>")
|
||||
print(" GET /api/v1/history")
|
||||
print(" POST /api/v1/forecast")
|
||||
print(" GET /api/v1/status/<optimization_id>")
|
||||
|
||||
app.run(host='0.0.0.0', port=8082, debug=False)
|
||||
|
|
@ -0,0 +1,203 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
Mock SCADA Server for testing
|
||||
Simulates industrial SCADA system with process data and equipment control
|
||||
"""
|
||||
|
||||
import json
|
||||
import random
|
||||
import time
|
||||
from datetime import datetime
|
||||
from flask import Flask, jsonify, request
|
||||
|
||||
app = Flask(__name__)
|
||||
|
||||
# Mock SCADA data
|
||||
current_data = {
|
||||
"temperature": {"value": 75.5, "unit": "°C", "min": 50, "max": 100},
|
||||
"pressure": {"value": 2.5, "unit": "bar", "min": 1, "max": 5},
|
||||
"flow_rate": {"value": 120.0, "unit": "m³/h", "min": 50, "max": 200},
|
||||
"level": {"value": 65.0, "unit": "%", "min": 0, "max": 100},
|
||||
"power": {"value": 450.0, "unit": "kW", "min": 200, "max": 600},
|
||||
"status": {"value": "RUNNING", "unit": "-"},
|
||||
"efficiency": {"value": 87.5, "unit": "%", "min": 0, "max": 100}
|
||||
}
|
||||
|
||||
# Equipment status
|
||||
equipment_status = {
|
||||
"pump_1": "RUNNING",
|
||||
"pump_2": "STOPPED",
|
||||
"valve_1": "OPEN",
|
||||
"valve_2": "CLOSED",
|
||||
"compressor": "RUNNING",
|
||||
"heater": "STOPPED"
|
||||
}
|
||||
|
||||
# Alarm status
|
||||
alarms = []
|
||||
|
||||
def update_data():
|
||||
"""Update mock data with realistic variations"""
|
||||
global current_data
|
||||
|
||||
# Temperature variation
|
||||
current_data["temperature"]["value"] += random.uniform(-0.5, 0.5)
|
||||
current_data["temperature"]["value"] = max(50, min(100, current_data["temperature"]["value"]))
|
||||
|
||||
# Pressure variation
|
||||
current_data["pressure"]["value"] += random.uniform(-0.1, 0.1)
|
||||
current_data["pressure"]["value"] = max(1, min(5, current_data["pressure"]["value"]))
|
||||
|
||||
# Flow rate variation
|
||||
current_data["flow_rate"]["value"] += random.uniform(-2, 2)
|
||||
current_data["flow_rate"]["value"] = max(50, min(200, current_data["flow_rate"]["value"]))
|
||||
|
||||
# Level variation
|
||||
current_data["level"]["value"] += random.uniform(-1, 1)
|
||||
current_data["level"]["value"] = max(0, min(100, current_data["level"]["value"]))
|
||||
|
||||
# Power variation
|
||||
current_data["power"]["value"] += random.uniform(-5, 5)
|
||||
current_data["power"]["value"] = max(200, min(600, current_data["power"]["value"]))
|
||||
|
||||
# Efficiency variation
|
||||
current_data["efficiency"]["value"] += random.uniform(-0.5, 0.5)
|
||||
current_data["efficiency"]["value"] = max(0, min(100, current_data["efficiency"]["value"]))
|
||||
|
||||
# Check for alarm conditions
|
||||
check_alarms()
|
||||
|
||||
def check_alarms():
|
||||
"""Check for alarm conditions"""
|
||||
global alarms
|
||||
|
||||
# Clear old alarms
|
||||
alarms = [alarm for alarm in alarms if datetime.now().timestamp() - alarm["timestamp"] < 300]
|
||||
|
||||
# Check temperature alarm
|
||||
if current_data["temperature"]["value"] > 95:
|
||||
add_alarm("HIGH_TEMPERATURE", f"Temperature high: {current_data['temperature']['value']:.1f}°C")
|
||||
elif current_data["temperature"]["value"] < 55:
|
||||
add_alarm("LOW_TEMPERATURE", f"Temperature low: {current_data['temperature']['value']:.1f}°C")
|
||||
|
||||
# Check pressure alarm
|
||||
if current_data["pressure"]["value"] > 4.5:
|
||||
add_alarm("HIGH_PRESSURE", f"Pressure high: {current_data['pressure']['value']:.1f} bar")
|
||||
elif current_data["pressure"]["value"] < 1.5:
|
||||
add_alarm("LOW_PRESSURE", f"Pressure low: {current_data['pressure']['value']:.1f} bar")
|
||||
|
||||
# Check level alarm
|
||||
if current_data["level"]["value"] > 90:
|
||||
add_alarm("HIGH_LEVEL", f"Level high: {current_data['level']['value']:.1f}%")
|
||||
elif current_data["level"]["value"] < 20:
|
||||
add_alarm("LOW_LEVEL", f"Level low: {current_data['level']['value']:.1f}%")
|
||||
|
||||
def add_alarm(type, message):
|
||||
"""Add a new alarm"""
|
||||
global alarms
|
||||
|
||||
# Check if alarm already exists
|
||||
for alarm in alarms:
|
||||
if alarm["type"] == type:
|
||||
return
|
||||
|
||||
alarms.append({
|
||||
"type": type,
|
||||
"message": message,
|
||||
"timestamp": datetime.now().timestamp(),
|
||||
"acknowledged": False
|
||||
})
|
||||
|
||||
@app.route('/health', methods=['GET'])
|
||||
def health():
|
||||
"""Health check endpoint"""
|
||||
return jsonify({
|
||||
"status": "healthy",
|
||||
"service": "mock-scada",
|
||||
"timestamp": datetime.now().isoformat()
|
||||
})
|
||||
|
||||
@app.route('/api/v1/data', methods=['GET'])
|
||||
def get_all_data():
|
||||
"""Get all SCADA data"""
|
||||
update_data()
|
||||
|
||||
return jsonify({
|
||||
"timestamp": datetime.now().isoformat(),
|
||||
"data": current_data,
|
||||
"equipment": equipment_status
|
||||
})
|
||||
|
||||
@app.route('/api/v1/data/<tag>', methods=['GET'])
|
||||
def get_specific_data(tag):
|
||||
"""Get specific SCADA data tag"""
|
||||
update_data()
|
||||
|
||||
if tag not in current_data:
|
||||
return jsonify({"error": f"Tag '{tag}' not found"}), 404
|
||||
|
||||
return jsonify({
|
||||
"tag": tag,
|
||||
"value": current_data[tag]["value"],
|
||||
"unit": current_data[tag]["unit"],
|
||||
"timestamp": datetime.now().isoformat()
|
||||
})
|
||||
|
||||
@app.route('/api/v1/control/<equipment>', methods=['POST'])
|
||||
def control_equipment(equipment):
|
||||
"""Control SCADA equipment"""
|
||||
if equipment not in equipment_status:
|
||||
return jsonify({"error": f"Equipment '{equipment}' not found"}), 404
|
||||
|
||||
command = request.json.get("command")
|
||||
if not command:
|
||||
return jsonify({"error": "Command is required"}), 400
|
||||
|
||||
valid_commands = ["START", "STOP", "OPEN", "CLOSE", "RESET"]
|
||||
if command not in valid_commands:
|
||||
return jsonify({"error": f"Invalid command. Valid commands: {valid_commands}"}), 400
|
||||
|
||||
previous_status = equipment_status[equipment]
|
||||
equipment_status[equipment] = command
|
||||
|
||||
return jsonify({
|
||||
"equipment": equipment,
|
||||
"previous_status": previous_status,
|
||||
"current_status": command,
|
||||
"timestamp": datetime.now().isoformat(),
|
||||
"message": f"Equipment {equipment} changed from {previous_status} to {command}"
|
||||
})
|
||||
|
||||
@app.route('/api/v1/alarms', methods=['GET'])
|
||||
def get_alarms():
|
||||
"""Get current alarms"""
|
||||
return jsonify({
|
||||
"alarms": alarms,
|
||||
"timestamp": datetime.now().isoformat()
|
||||
})
|
||||
|
||||
@app.route('/api/v1/alarms/<alarm_type>/acknowledge', methods=['POST'])
|
||||
def acknowledge_alarm(alarm_type):
|
||||
"""Acknowledge an alarm"""
|
||||
for alarm in alarms:
|
||||
if alarm["type"] == alarm_type:
|
||||
alarm["acknowledged"] = True
|
||||
return jsonify({
|
||||
"alarm": alarm_type,
|
||||
"acknowledged": True,
|
||||
"timestamp": datetime.now().isoformat()
|
||||
})
|
||||
|
||||
return jsonify({"error": f"Alarm '{alarm_type}' not found"}), 404
|
||||
|
||||
if __name__ == '__main__':
|
||||
print("🚀 Starting Mock SCADA Server on port 8081...")
|
||||
print("📊 Available endpoints:")
|
||||
print(" GET /health")
|
||||
print(" GET /api/v1/data")
|
||||
print(" GET /api/v1/data/<tag>")
|
||||
print(" POST /api/v1/control/<equipment>")
|
||||
print(" GET /api/v1/alarms")
|
||||
print(" POST /api/v1/alarms/<type>/acknowledge")
|
||||
|
||||
app.run(host='0.0.0.0', port=8081, debug=False)
|
||||
Loading…
Reference in New Issue