Developer Guide
Development Environment Setup
Prerequisites
- Python 3.11+
- PostgreSQL (for development)
- Git
- Code editor (VS Code recommended)
Quick Setup
# Clone repository
git clone https://github.com/your-org/race-management-console.git
cd race-management-console
# Setup virtual environment
python3.11 -m venv venv
source venv/bin/activate
# Install dependencies
pip install -r requirements.txt
# Setup environment variables
cp .env.example .env
# Edit .env with your configuration
# Initialize database
python -c "from app import app, db; app.app_context().push(); db.create_all()"
# Run development server
flask run --debug --host=0.0.0.0 --port=5000
Project Structure
race-management-console/
├── app.py # Flask application factory
├── main.py # Application entry point
├── models.py # Database models
├── routes.py # API routes and handlers
├── config/ # Configuration files
│ ├── app_config.json # Application settings
│ └── system_tags.json # System tag definitions
├── services/ # Business logic services
│ ├── ai_conversation.py # AI conversation handling
│ ├── aveva_client.py # CONNECT API client
│ ├── config_manager.py # Configuration management
│ ├── context_extractor.py # MES context extraction
│ ├── event_enrichment.py # Event data enrichment
│ ├── function_calling.py # AI function calling
│ ├── monitoring_engine.py # Background monitoring
│ ├── placeholder_resolver.py # Template placeholders
│ ├── rule_engine.py # Rule evaluation engine
│ └── system_tags.py # System tag management
├── templates/ # Jinja2 templates
│ ├── base.html # Base template
│ ├── index.html # Dashboard
│ ├── configuration/ # Configuration pages
│ ├── rules/ # Rule management pages
│ ├── events/ # Event monitoring pages
│ └── cognition/ # AI interface pages
├── static/ # Static assets
│ ├── css/ # Stylesheets
│ ├── js/ # JavaScript files
│ └── images/ # Images and icons
├── docs/ # Documentation
├── tests/ # Test suite
├── requirements.txt # Python dependencies
└── .env.example # Environment template
Database Models
Core Models
Package
class Package(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(100), nullable=False)
description = db.Column(db.Text)
created_at = db.Column(db.DateTime, default=datetime.utcnow)
# Relationships
templates = db.relationship('RuleTemplate', backref='package', lazy=True)
RuleTemplate
class RuleTemplate(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(100), nullable=False)
category = db.Column(db.String(50), nullable=False)
color = db.Column(db.String(7), default='#007bff')
description = db.Column(db.Text)
package_id = db.Column(db.Integer, db.ForeignKey('package.id'), nullable=False)
# Relationships
rules = db.relationship('Rule', backref='template', lazy=True)
instances = db.relationship('TemplateInstance', backref='template', lazy=True)
TemplateInstance
class TemplateInstance(db.Model):
id = db.Column(db.Integer, primary_key=True)
instance_name = db.Column(db.String(100), nullable=False)
template_id = db.Column(db.Integer, db.ForeignKey('rule_template.id'), nullable=False)
plant_node_floc_id = db.Column(db.String(100), nullable=False)
is_deployed = db.Column(db.Boolean, default=False)
# Relationships
placeholders = db.relationship('TemplatePlaceholder', backref='instance', lazy=True)
events = db.relationship('RuleEvent', backref='template_instance', lazy=True)
Adding New Models
- Define Model Class
class NewModel(db.Model):
__tablename__ = 'new_models'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(100), nullable=False)
created_at = db.Column(db.DateTime, default=datetime.utcnow)
def to_dict(self):
return {
'id': self.id,
'name': self.name,
'created_at': self.created_at.isoformat() if self.created_at else None
}
- Create Migration
# In flask shell or migration script
from app import app, db
with app.app_context():
db.create_all()
- Add to models.py imports
# Update routes.py import statement
from models import NewModel, ...
Service Layer Development
Creating New Services
- Service Structure
# services/new_service.py
import logging
from typing import Dict, List, Optional
logger = logging.getLogger(__name__)
class NewService:
def __init__(self):
self.config = {}
def process_data(self, data: Dict) -> Dict:
"""Process data with error handling and logging"""
try:
logger.info(f"Processing data: {data}")
# Implementation here
result = self._internal_processing(data)
logger.info("Processing completed successfully")
return result
except Exception as e:
logger.error(f"Error processing data: {str(e)}")
raise
def _internal_processing(self, data: Dict) -> Dict:
"""Private method for internal processing"""
return {"processed": True, "data": data}
- Service Registration
# In app.py or main.py
from services.new_service import NewService
# Initialize service
new_service = NewService()
- Using in Routes
# In routes.py
from services.new_service import NewService
@app.route('/api/new-endpoint')
def new_endpoint():
try:
service = NewService()
result = service.process_data(request.json)
return jsonify({'success': True, 'data': result})
except Exception as e:
return jsonify({'success': False, 'error': str(e)}), 500
AI Service Integration
Adding New AI Providers
- Extend AIConversationService
# services/ai_conversation.py
class AIConversationService:
def _call_new_provider(self, provider: AIProvider, messages: List[Dict], functions: List[Dict] = None) -> Dict:
"""Add support for new AI provider"""
try:
# Initialize provider client
client = NewProviderClient(api_key=provider.api_key)
# Format messages for provider
formatted_messages = self._format_messages_for_new_provider(messages)
# Make API call
response = client.chat.completions.create(
model=provider.model_name,
messages=formatted_messages,
max_tokens=provider.max_tokens,
temperature=provider.temperature,
functions=functions if provider.supports_function_calling else None
)
return self._process_new_provider_response(response)
except Exception as e:
logger.error(f"Error calling new provider: {str(e)}")
raise
- Update Provider Type Handling
def call_ai_provider(self, provider: AIProvider, messages: List[Dict], functions: List[Dict] = None) -> Dict:
"""Route to appropriate provider implementation"""
if provider.provider_type == 'openai':
return self._call_openai(provider, messages, functions)
elif provider.provider_type == 'anthropic':
return self._call_anthropic(provider, messages, functions)
elif provider.provider_type == 'new_provider':
return self._call_new_provider(provider, messages, functions)
else:
raise ValueError(f"Unsupported provider type: {provider.provider_type}")
Function Calling Development
Adding New Functions
- Define Function Schema
# services/function_calling.py
def get_equipment_diagnostics(equipment_id: str, diagnostic_type: str = "all") -> Dict:
"""
Get detailed diagnostic information for specific equipment
Args:
equipment_id: Equipment identifier (e.g., 'Roaster022')
diagnostic_type: Type of diagnostics ('performance', 'health', 'all')
Returns:
Dict containing diagnostic data
"""
function_schema = {
"name": "get_equipment_diagnostics",
"description": "Get detailed diagnostic information for specific equipment",
"parameters": {
"type": "object",
"properties": {
"equipment_id": {
"type": "string",
"description": "Equipment identifier"
},
"diagnostic_type": {
"type": "string",
"enum": ["performance", "health", "all"],
"description": "Type of diagnostics to retrieve"
}
},
"required": ["equipment_id"]
}
}
return function_schema
- Implement Function Logic
class FunctionCallingService:
def execute_function(self, function_name: str, arguments: Dict) -> Dict:
"""Execute function with error handling"""
try:
if function_name == "get_equipment_diagnostics":
return self._get_equipment_diagnostics(**arguments)
else:
raise ValueError(f"Unknown function: {function_name}")
except Exception as e:
logger.error(f"Function execution error: {str(e)}")
return {"error": str(e)}
def _get_equipment_diagnostics(self, equipment_id: str, diagnostic_type: str = "all") -> Dict:
"""Implementation of equipment diagnostics function"""
# Query database for equipment data
streams = MonitoredStream.query.filter(
MonitoredStream.stream_name.like(f"%{equipment_id}%")
).all()
# Gather diagnostic data
diagnostics = {
"equipment_id": equipment_id,
"diagnostic_type": diagnostic_type,
"timestamp": datetime.utcnow().isoformat(),
"data": {}
}
# Add specific diagnostic logic here
return diagnostics
Frontend Development
JavaScript Structure
Component Pattern
// static/js/components/event-wall.js
class EventWall {
constructor(containerId, options = {}) {
this.container = document.getElementById(containerId);
this.options = {
refreshInterval: 30000,
maxEvents: 100,
...options
};
this.eventData = [];
this.init();
}
init() {
this.setupEventListeners();
this.loadEvents();
this.startAutoRefresh();
}
setupEventListeners() {
// Event listeners for UI interactions
}
async loadEvents() {
try {
const response = await fetch('/api/event-wall-data');
const data = await response.json();
if (data.success) {
this.eventData = data.data;
this.render();
}
} catch (error) {
console.error('Error loading events:', error);
}
}
render() {
// Render event wall visualization
}
}
Usage Pattern
// In template or main.js
document.addEventListener('DOMContentLoaded', function() {
const eventWall = new EventWall('event-wall-container', {
refreshInterval: 15000,
layout: 'template'
});
});
CSS Organization
Component Styles
/* static/css/components/event-wall.css */
.event-wall {
--primary-color: #007bff;
--warning-color: #ffc107;
--error-color: #dc3545;
--success-color: #28a745;
}
.event-wall__container {
display: flex;
flex-direction: column;
height: 100%;
}
.event-wall__timeline {
flex: 1;
overflow-y: auto;
}
.event-wall__event {
padding: 0.75rem;
margin-bottom: 0.5rem;
border-radius: 0.25rem;
transition: all 0.2s ease;
}
.event-wall__event--active {
border-left: 4px solid var(--primary-color);
background-color: rgba(0, 123, 255, 0.1);
}
.event-wall__event--warning {
border-left: 4px solid var(--warning-color);
background-color: rgba(255, 193, 7, 0.1);
}
Template Development
Base Template Structure
<!-- templates/base.html -->
<!DOCTYPE html>
<html lang="en" data-bs-theme="dark">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}RACE Management Console{% endblock %}</title>
<!-- Bootstrap CSS -->
<link href="https://cdn.replit.com/agent/bootstrap-agent-dark-theme.min.css" rel="stylesheet">
<!-- Custom CSS -->
<link rel="stylesheet" href="{{ url_for('static', filename='css/main.css') }}?v={{ cache_buster }}">
{% block extra_css %}{% endblock %}
</head>
<body>
<!-- Navigation -->
{% include 'partials/navigation.html' %}
<!-- Main Content -->
<main class="container-fluid py-4">
{% block content %}{% endblock %}
</main>
<!-- JavaScript -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
<script src="https://unpkg.com/feather-icons"></script>
<script>feather.replace();</script>
{% block extra_js %}{% endblock %}
</body>
</html>
Page Template Pattern
<!-- templates/new-page.html -->
{% extends "base.html" %}
{% block title %}New Page - RACE Console{% endblock %}
{% block extra_css %}
<link rel="stylesheet" href="{{ url_for('static', filename='css/new-page.css') }}?v={{ cache_buster }}">
{% endblock %}
{% block content %}
<div class="row">
<div class="col-12">
<div class="card">
<div class="card-header">
<h5 class="card-title mb-0">
<i data-feather="icon-name" class="me-2"></i>Page Title
</h5>
</div>
<div class="card-body">
<!-- Page content -->
</div>
</div>
</div>
</div>
{% endblock %}
{% block extra_js %}
<script src="{{ url_for('static', filename='js/new-page.js') }}?v={{ cache_buster }}"></script>
{% endblock %}
Form Builder Development
Architecture Overview
The Dynamic Form Builder follows a modular service-oriented architecture with clear separation between AI generation, form management, and runtime execution.
Core Components
Database Models:
# Form registry for metadata
class FormRegistry(db.Model):
form_id = db.Column(db.String(200), primary_key=True)
latest_version = db.Column(db.String(20))
created_by = db.Column(db.String(200))
# Individual form versions with content
class FormVersion(db.Model):
id = db.Column(db.String(36), primary_key=True)
form_id = db.Column(db.String(200))
version = db.Column(db.String(20)) # major.minor.patch.build
status = db.Column(db.String(20)) # draft, published
content = db.Column(JSON) # Complete form JSON
grid_layout = db.Column(JSON) # Drag & drop layout
checksum = db.Column(db.String(64)) # Content integrity
Service Layer:
- ai_form_authoring.py: AI conversation management and JSON generation
- widget_renderer.py: Runtime widget rendering with grid layout support
- form_data_handler.py: Form data validation and processing
- form_schema.py: JSON schema validation with RACE-specific rules
Database Models (Complete Schema):
class FormRegistry(db.Model):
form_id = db.Column(db.String(200), primary_key=True)
latest_version = db.Column(db.String(20)) # e.g. "2.1.0.5"
created_by = db.Column(db.String(200))
created_at = db.Column(db.DateTime)
updated_at = db.Column(db.DateTime)
class FormVersion(db.Model):
id = db.Column(db.String(36), primary_key=True)
form_id = db.Column(db.String(200))
version = db.Column(db.String(20)) # "major.minor.patch.build"
status = db.Column(db.String(20)) # "draft" | "published"
content = db.Column(JSON) # Complete form JSON
grid_layout = db.Column(JSON) # Version-specific layout
checksum = db.Column(db.String(64)) # Content integrity
conversation_id = db.Column(db.String(36)) # AI conversation link
class FormPermission(db.Model):
id = db.Column(db.String(36), primary_key=True)
form_id = db.Column(db.String(200))
user_role = db.Column(db.String(100)) # RBAC integration
permission_level = db.Column(db.String(50))
4-Digit Versioning System
Version Structure: major.minor.patch.build
- User controls: Major, Minor, Patch via UI buttons
- System controls: Build auto-increment via checkbox
- Independent status management per version
Grid Layout System
Version-Specific Storage: - Each FormVersion has independent grid_layout JSON - Version selection clears localStorage to force database reload - CSS Grid positioning with exact cell placement
API Endpoints
Version Management APIs
# Save grid layout with version targeting and auto-INSERT
POST /form-builder/api/save-grid-layout/{form_id}
Body: {
"grid_layout": {
"rows": 5, "cols": 7,
"cells": [{"id": "cell_0_0", "row": 0, "col": 0, "widget_id": "name_123"}]
},
"version": "2.1.0.3", # X.X.X.X format
"status": "draft" # draft/published
}
Response: {
"success": true,
"message": "Grid layout saved and version created",
"version_created": true
}
# Update version status only (no content modification)
POST /form-builder/api/update-version-status/{form_id}
Body: {
"version": "2.1.0.3",
"status": "published"
}
Response: {
"success": true,
"message": "Version status updated",
"old_status": "draft",
"new_status": "published"
}
# Load specific version with ver parameter
GET /form-builder/forms/{form_id}?ver=2.1.0.3
Response: FormVersion object with version-specific content and grid_layout
# Combined runtime access
GET /form-builder/runtime/{form_id}?ver=2.1.0.3&status=draft
# Loads specific version with status filtering
Runtime System
# Unified runtime with version support
GET /form-builder/runtime/{form_id}?ver=X.X.X.X&status=draft
# Version parameter examples
GET /form-builder/runtime/maintenance_form?ver=2.1.0.3&status=draft
GET /form-builder/runtime/inspection_checklist?ver=1.0.0.1&status=published
Frontend Architecture
Version Management
// Global version state
let currentVersionData = {
major: 1, minor: 0, patch: 0, build: 0
};
// Version selection with cache management
function loadVersion(selectedVersion) {
// Clear localStorage to force DB reload
const layoutKey = `gridLayout_${formId}`;
localStorage.removeItem(layoutKey);
// Reload with version parameter
const url = new URL(window.location);
url.searchParams.set('version', selectedVersion);
window.location.href = url.toString();
}
Build Increment Control
// Conditional build increment
async function saveFormChanges() {
const autoIncrementCheckbox = document.getElementById('autoIncrementBuild');
let buildWasIncremented = false;
if (autoIncrementCheckbox?.checked) {
currentVersionData.build++;
updateVersionDisplay();
buildWasIncremented = true;
}
// Save logic with error rollback
}
API Development
Route Structure
Standard Route Pattern
@app.route('/api/resource', methods=['GET', 'POST'])
def handle_resource():
"""Handle resource operations with proper error handling"""
try:
if request.method == 'GET':
return get_resource()
elif request.method == 'POST':
return create_resource()
except Exception as e:
logger.error(f"Error in handle_resource: {str(e)}")
return jsonify({'success': False, 'error': str(e)}), 500
def get_resource():
"""Get resource with pagination and filtering"""
# Pagination
page = request.args.get('page', 1, type=int)
per_page = request.args.get('per_page', 20, type=int)
# Filtering
filter_param = request.args.get('filter')
# Query building
query = Resource.query
if filter_param:
query = query.filter(Resource.name.contains(filter_param))
# Execute query with pagination
resources = query.paginate(
page=page,
per_page=per_page,
error_out=False
)
return jsonify({
'success': True,
'data': [r.to_dict() for r in resources.items],
'pagination': {
'page': page,
'per_page': per_page,
'total': resources.total,
'pages': resources.pages,
'has_next': resources.has_next,
'has_prev': resources.has_prev
}
})
def create_resource():
"""Create new resource with validation"""
data = request.get_json()
# Validation
if not data or 'name' not in data:
return jsonify({'success': False, 'error': 'Name is required'}), 400
# Create resource
resource = Resource(name=data['name'])
db.session.add(resource)
db.session.commit()
return jsonify({
'success': True,
'data': resource.to_dict(),
'message': 'Resource created successfully'
}), 201
Error Handling
Custom Exception Classes
# exceptions.py
class RaceConsoleException(Exception):
"""Base exception for RACE Console"""
pass
class ConfigurationError(RaceConsoleException):
"""Configuration-related errors"""
pass
class APIConnectionError(RaceConsoleException):
"""External API connection errors"""
pass
class RuleEvaluationError(RaceConsoleException):
"""Rule evaluation errors"""
pass
Error Handler Registration
# app.py
@app.errorhandler(APIConnectionError)
def handle_api_connection_error(e):
logger.error(f"API Connection Error: {str(e)}")
return jsonify({
'success': False,
'error': 'External API connection failed',
'details': str(e)
}), 503
@app.errorhandler(ValidationError)
def handle_validation_error(e):
return jsonify({
'success': False,
'error': 'Validation failed',
'details': e.messages
}), 400
Testing
Unit Tests
Test Structure
# tests/test_rule_engine.py
import pytest
from unittest.mock import Mock, patch
from services.rule_engine import RuleEngine
from models import Rule, TemplateInstance
class TestRuleEngine:
def setup_method(self):
"""Setup test environment"""
self.rule_engine = RuleEngine()
self.mock_rule = Mock(spec=Rule)
self.mock_instance = Mock(spec=TemplateInstance)
def test_evaluate_condition_equals(self):
"""Test equality condition evaluation"""
# Arrange
condition = {
'attribute': 'Status',
'operator': 'equals',
'value': 'Running'
}
stream_value = 'Running'
# Act
result = self.rule_engine._evaluate_condition(condition, stream_value)
# Assert
assert result is True
@patch('services.rule_engine.RuleEvent')
def test_trigger_rule_creates_event(self, mock_rule_event):
"""Test that rule triggering creates events"""
# Arrange
self.mock_rule.event_name = 'Test Event'
self.mock_rule.severity = 'info'
# Act
self.rule_engine.trigger_rule(self.mock_rule, self.mock_instance, 'test_value')
# Assert
mock_rule_event.assert_called_once()
Running Tests
# Install test dependencies
pip install pytest pytest-cov pytest-mock
# Run all tests
pytest
# Run with coverage
pytest --cov=. --cov-report=html
# Run specific test file
pytest tests/test_rule_engine.py
# Run specific test
pytest tests/test_rule_engine.py::TestRuleEngine::test_evaluate_condition_equals
Integration Tests
API Testing
# tests/test_api.py
import pytest
from app import app, db
@pytest.fixture
def client():
"""Test client fixture"""
app.config['TESTING'] = True
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///:memory:'
with app.test_client() as client:
with app.app_context():
db.create_all()
yield client
db.drop_all()
def test_get_packages(client):
"""Test packages API endpoint"""
response = client.get('/api/packages')
assert response.status_code == 200
data = response.get_json()
assert data['success'] is True
assert 'data' in data
def test_create_package(client):
"""Test package creation"""
package_data = {
'name': 'Test Package',
'description': 'Test description'
}
response = client.post('/api/packages', json=package_data)
assert response.status_code == 201
data = response.get_json()
assert data['success'] is True
assert data['data']['name'] == 'Test Package'
Debugging
Logging Configuration
Setup Structured Logging
# config/logging.py
import logging
import logging.config
LOGGING_CONFIG = {
'version': 1,
'disable_existing_loggers': False,
'formatters': {
'standard': {
'format': '%(asctime)s [%(levelname)s] %(name)s: %(message)s'
},
'detailed': {
'format': '%(asctime)s [%(levelname)s] %(name)s:%(lineno)d: %(message)s'
}
},
'handlers': {
'console': {
'level': 'INFO',
'class': 'logging.StreamHandler',
'formatter': 'standard'
},
'file': {
'level': 'DEBUG',
'class': 'logging.FileHandler',
'filename': 'app.log',
'formatter': 'detailed'
}
},
'loggers': {
'': {
'handlers': ['console', 'file'],
'level': 'DEBUG',
'propagate': False
},
'services': {
'handlers': ['console', 'file'],
'level': 'DEBUG',
'propagate': False
}
}
}
def setup_logging():
logging.config.dictConfig(LOGGING_CONFIG)
Performance Profiling
Flask-Profiler Integration
# app.py
from flask_profiler import Profiler
# Add to application factory
if app.config.get('PROFILING_ENABLED'):
app.config['flask_profiler'] = {
'enabled': True,
'storage': {
'engine': 'sqlite'
},
'basicAuth': {
'enabled': True,
'username': 'admin',
'password': 'admin'
}
}
profiler = Profiler()
profiler.init_app(app)
Database Debugging
Query Logging
# Enable SQL query logging
import logging
logging.getLogger('sqlalchemy.engine').setLevel(logging.INFO)
# Or in app configuration
app.config['SQLALCHEMY_ECHO'] = True
Database Connection Debugging
# services/database_debug.py
def debug_database_connection():
"""Debug database connection issues"""
try:
from app import db
result = db.session.execute('SELECT 1')
logger.info("Database connection successful")
return True
except Exception as e:
logger.error(f"Database connection failed: {str(e)}")
return False
Deployment
Production Checklist
Code Quality
- [ ] All tests passing
- [ ] Code coverage > 80%
- [ ] No debug statements in production code
- [ ] Proper error handling
- [ ] Security review completed
Configuration
- [ ] Environment variables set
- [ ] Database migrations applied
- [ ] SSL certificates configured
- [ ] Firewall rules configured
- [ ] Monitoring setup
Performance
- [ ] Database indexes optimized
- [ ] Static file caching configured
- [ ] Application performance tested
- [ ] Load testing completed
Continuous Integration
GitHub Actions Example
# .github/workflows/test.yml
name: Test Suite
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
services:
postgres:
image: postgres:13
env:
POSTGRES_PASSWORD: postgres
POSTGRES_DB: test_db
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
steps:
- uses: actions/checkout@v2
- name: Set up Python
uses: actions/setup-python@v2
with:
python-version: 3.11
- name: Install dependencies
run: |
pip install -r requirements.txt
pip install pytest pytest-cov
- name: Run tests
run: pytest --cov=. --cov-report=xml
env:
DATABASE_URL: postgresql://postgres:postgres@localhost:5432/test_db
- name: Upload coverage
uses: codecov/codecov-action@v1
Contributing
Code Style
Python Style Guide
- Follow PEP 8
- Use type hints where appropriate
- Maximum line length: 120 characters
- Use descriptive variable names
- Document complex functions
JavaScript Style Guide
- Use ES6+ features
- Consistent indentation (2 spaces)
- Use meaningful variable names
- Comment complex logic
Git Workflow
- Create feature branch from main
- Make commits with descriptive messages
- Create pull request
- Code review and approval
- Merge to main
Pull Request Template
## Description
Brief description of changes
## Type of Change
- [ ] Bug fix
- [ ] New feature
- [ ] Breaking change
- [ ] Documentation update
## Testing
- [ ] Unit tests added/updated
- [ ] Integration tests added/updated
- [ ] Manual testing completed
## Checklist
- [ ] Code follows style guidelines
- [ ] Self-review completed
- [ ] Documentation updated
- [ ] Tests pass locally