codenuk_backend_mine/services/architecture-designer/core/router.py

331 lines
16 KiB
Python

# FIXED DYNAMIC TECHNOLOGY ROUTER - Correctly extracts from YOUR tech-stack-selector
# Fixed paths and structure to match your actual tech-stack-selector response
from typing import Dict, Any
from loguru import logger
# Import dynamic technology specialists
from designers.frontend.react_designer import ReactDesigner
from designers.backend.nodejs_designer import NodejsDesigner
from designers.database.postgresql_designer import PostgreSQLDesigner
from designers.database.mongodb_designer import DynamicMongoDBDesigner
from designers.frontend.angular_designer_18 import Angular18Designer
from designers.backend.aspnet_designer_8 import AspNetCore8Designer
from designers.database.mssql_designer_2022 import MSSQLServer2022Designer
class TechnologyStack:
"""Technology stack specification extracted from tech-stack-selector"""
def __init__(self, tech_recommendations: Dict[str, Any]):
# Extract from YOUR exact tech-stack-selector structure
frontend_config = tech_recommendations.get('frontend', {})
backend_config = tech_recommendations.get('backend', {})
database_config = tech_recommendations.get('database', {})
security_config = tech_recommendations.get('security', {})
infrastructure_config = tech_recommendations.get('infrastructure', {})
# Extract exact values from your response
self.frontend_framework = frontend_config.get('framework', 'React').lower()
self.backend_language = backend_config.get('language', 'Node.js').lower()
self.database_system = database_config.get('primary', 'PostgreSQL').lower()
# Extract additional tech stack details
self.ui_library = self._extract_ui_library(frontend_config.get('libraries', []))
self.state_management = self._extract_state_management(frontend_config.get('libraries', []))
self.authentication = security_config.get('authentication', 'JWT')
self.cloud_provider = infrastructure_config.get('cloud_provider', 'AWS')
logger.info(f"✅ Technology Stack extracted:")
logger.info(f" Frontend: {self.frontend_framework}")
logger.info(f" Backend: {self.backend_language}")
logger.info(f" Database: {self.database_system}")
logger.info(f" UI Library: {self.ui_library}")
logger.info(f" State Management: {self.state_management}")
logger.info(f" Authentication: {self.authentication}")
def _extract_ui_library(self, libraries: list) -> str:
"""Extract UI library from frontend libraries"""
ui_libraries = ['tailwind css', 'material-ui', 'chakra ui', 'ant design', 'bootstrap']
for lib in libraries:
if any(ui_lib in lib.lower() for ui_lib in ui_libraries):
return lib
return 'Tailwind CSS' # Default
def _extract_state_management(self, libraries: list) -> str:
"""Extract state management from frontend libraries"""
state_libs = ['redux toolkit', 'zustand', 'context api', 'recoil', 'jotai']
for lib in libraries:
if any(state_lib in lib.lower() for state_lib in state_libs):
return lib
return 'Redux Toolkit' # Default for complex apps
class TechnologyRouter:
"""FIXED router that correctly handles YOUR tech-stack-selector output"""
def __init__(self):
# Initialize available specialists
self.frontend_specialists = {
"react": ReactDesigner(),
"angular": Angular18Designer(), # NEW
"angular 18": Angular18Designer(), # NEW
"angular18": Angular18Designer(), # NEW
"vue": None, # Will add later
}
self.backend_specialists = {
"node.js": NodejsDesigner(),
"asp.net": AspNetCore8Designer(), # NEW
"asp.net core": AspNetCore8Designer(), # NEW
"aspnet": AspNetCore8Designer(), # NEW
"c#": AspNetCore8Designer(), # NEW
"nodejs": NodejsDesigner(),
"python": None, # Will add later
"java": None, # Will add later
}
self.database_specialists = {
"postgresql": PostgreSQLDesigner(),
"postgres": PostgreSQLDesigner(),
"mysql": None, # Will add later
"ms sql server": MSSQLServer2022Designer(), # NEW
"ms sql server 2022": MSSQLServer2022Designer(), # NEW
"mssql": MSSQLServer2022Designer(), # NEW
"sqlserver": MSSQLServer2022Designer(), # NEW
"sql server": MSSQLServer2022Designer(), # NEW
"mongodb": DynamicMongoDBDesigner(), # Will add later
"postgresql": PostgreSQLDesigner(),
"postgres": PostgreSQLDesigner(),
"PostgreSQL": PostgreSQLDesigner(), # Add uppercase
"Postgres": PostgreSQLDesigner(), # Add mixed case
"mongodb": DynamicMongoDBDesigner(),
"MongoDB": DynamicMongoDBDesigner(),
}
logger.info("🔄 FIXED Technology Router initialized")
logger.info(f" Available Frontend: {[k for k, v in self.frontend_specialists.items() if v]}")
logger.info(f" Available Backend: {[k for k, v in self.backend_specialists.items() if v]}")
logger.info(f" Available Database: {[k for k, v in self.database_specialists.items() if v]}")
def extract_technology_stack(self, tech_stack_selector_output: Dict[str, Any]) -> TechnologyStack:
"""Extract technology stack from YOUR tech-stack-selector response - WITH USER EDITS SUPPORT"""
try:
logger.info("🎯 Extracting technology stack from tech-stack-selector...")
# CHECK FOR USER'S EDITED TECH STACK FIRST
user_tech_choices = tech_stack_selector_output.get('user_technology_choices')
if user_tech_choices:
logger.info("✅ Found user's edited technology choices - using them!")
logger.info(f" User Frontend: {user_tech_choices.get('frontend', {}).get('framework', 'Unknown')}")
logger.info(f" User Backend: {user_tech_choices.get('backend', {}).get('language', 'Unknown')}")
logger.info(f" User Database: {user_tech_choices.get('database', {}).get('primary', 'Unknown')}")
# Use user's edited tech stack
tech_stack = TechnologyStack(user_tech_choices)
else:
logger.info("⚠️ No user edits found, using original AI recommendations...")
# Fallback to original logic
claude_recommendations = tech_stack_selector_output.get('claude_recommendations', {})
technology_recommendations = claude_recommendations.get('technology_recommendations', {})
if not technology_recommendations:
logger.warning("⚠️ No technology_recommendations found, checking alternative paths...")
if 'frontend' in claude_recommendations:
technology_recommendations = claude_recommendations
else:
logger.error("❌ Cannot find technology recommendations in response")
raise ValueError("No technology recommendations found in tech-stack-selector output")
tech_stack = TechnologyStack(technology_recommendations)
return tech_stack
except Exception as e:
logger.error(f"❌ Technology stack extraction failed: {e}")
logger.error(f" Available keys in response: {list(tech_stack_selector_output.keys())}")
raise
async def route_to_specialists(self, tech_stack: TechnologyStack,
functional_requirements: Dict[str, Any],
business_context: Dict[str, Any]) -> Dict[str, Any]:
"""Route to appropriate specialists based on extracted technology stack"""
try:
logger.info("🤖 Routing to technology specialists...")
# Get appropriate specialists
frontend_specialist = self._get_specialist(
tech_stack.frontend_framework,
self.frontend_specialists,
'frontend'
)
backend_specialist = self._get_specialist(
tech_stack.backend_language,
self.backend_specialists,
'backend'
)
database_specialist = self._get_specialist(
tech_stack.database_system,
self.database_specialists,
'database'
)
# Prepare context for specialists (using YOUR data structure)
design_context = self._prepare_design_context(
tech_stack, functional_requirements, business_context
)
# Call specialists in parallel
logger.info("🎨 Calling Frontend specialist...")
frontend_result = await frontend_specialist.design_architecture(design_context)
logger.info("⚙️ Calling Backend specialist...")
backend_result = await backend_specialist.design_architecture(design_context)
logger.info("🗄️ Calling Database specialist...")
database_result = await database_specialist.design_architecture(design_context)
logger.info("✅ All specialists completed successfully")
return {
'frontend': frontend_result,
'backend': backend_result,
'database': database_result
}
except Exception as e:
logger.error(f"❌ Specialist routing failed: {e}")
raise
def _get_specialist(self, technology: str, specialists_dict: Dict, specialist_type: str):
"""Get appropriate specialist for the technology"""
technology_key = technology.lower().replace('.', '').replace(' ', '')
specialist = specialists_dict.get(technology_key)
if specialist is None:
logger.warning(f"⚠️ No {specialist_type} specialist found for '{technology}', using fallback")
# Use first available specialist as fallback
for key, spec in specialists_dict.items():
if spec is not None:
logger.info(f" Using {key} specialist as fallback")
return spec
raise Exception(f"No {specialist_type} specialists available")
logger.info(f"✅ Using {technology_key} specialist for {specialist_type}")
return specialist
def _prepare_design_context(self, tech_stack: TechnologyStack,
functional_requirements: Dict[str, Any],
business_context: Dict[str, Any]) -> Dict[str, Any]:
"""Prepare context using YOUR tech-stack-selector data structure"""
return {
'project_id': 'generated_project',
# Technology stack information
'technology_stack': {
'frontend': {
'framework': tech_stack.frontend_framework,
'libraries': [tech_stack.ui_library, tech_stack.state_management]
},
'backend': {
'language': tech_stack.backend_language,
'framework': 'Express.js' if 'node' in tech_stack.backend_language else tech_stack.backend_language
},
'database': {
'primary': tech_stack.database_system
},
'security': {
'authentication': tech_stack.authentication
},
'infrastructure': {
'cloud_provider': tech_stack.cloud_provider
}
},
# Functional requirements from YOUR structure
'functional_requirements': {
'feature_name': functional_requirements.get('feature_name', 'Unknown'),
'description': functional_requirements.get('description', ''),
'technical_requirements': functional_requirements.get('technical_requirements', []),
'business_logic_rules': functional_requirements.get('business_logic_rules', []),
'complexity_level': functional_requirements.get('complexity_level', 'medium'),
'all_features': functional_requirements.get('all_features', [])
},
# Business context from YOUR structure
'business_context': business_context
}
async def route_and_design(self, tech_stack_output: Dict[str, Any], project_id: str) -> Dict[str, Any]:
"""MAIN ENTRY POINT - takes YOUR tech-stack-selector output and routes to specialists"""
try:
logger.info("🏗️ Starting architecture design with tech-stack-selector output...")
# Extract technology stack from YOUR response
tech_stack = self.extract_technology_stack(tech_stack_output)
# Extract functional requirements from YOUR response
functional_requirements = tech_stack_output.get('functional_requirements', {})
business_context = tech_stack_output.get('claude_recommendations', {})
# Route to specialists
specialist_results = await self.route_to_specialists(
tech_stack, functional_requirements, business_context
)
# Combine results
combined_result = {
'technologies_used': {
'frontend': tech_stack.frontend_framework,
'backend': tech_stack.backend_language,
'database': tech_stack.database_system
},
'technology_specifications': tech_stack.__dict__,
'architecture_design': {
'frontend_architecture': specialist_results['frontend'].get('architecture', {}),
'backend_architecture': specialist_results['backend'].get('architecture', {}),
'database_architecture': specialist_results['database'].get('architecture', {})
},
'specialist_results': specialist_results,
'integration_ready': True,
'source': 'tech_stack_selector_v11'
}
logger.info("✅ Architecture design completed successfully")
return combined_result
except Exception as e:
logger.error(f"❌ Architecture design failed: {e}")
return self._create_fallback_result(tech_stack_output)
def _create_fallback_result(self, tech_stack_output: Dict[str, Any]) -> Dict[str, Any]:
"""Create fallback result when routing fails"""
logger.warning("⚠️ Creating fallback architecture result")
return {
'technologies_used': {'frontend': 'react', 'backend': 'node.js', 'database': 'postgresql'},
'technology_specifications': {
'frontend_framework': 'react',
'backend_language': 'node.js',
'database_system': 'postgresql'
},
'architecture_design': {
'frontend_architecture': {'framework': 'React', 'note': 'Fallback architecture'},
'backend_architecture': {'framework': 'Node.js/Express', 'note': 'Fallback architecture'},
'database_architecture': {'system': 'PostgreSQL', 'note': 'Fallback architecture'}
},
'fallback': True,
'source': 'fallback_due_to_error'
}