codenuk_backend_mine/services/template-manager/ai/tech_stack_service.py
2025-09-26 17:04:14 +05:30

2032 lines
89 KiB
Python

# Copied from template-manager (2)/template-manager/tech_stack_service.py
# See original for full implementation details
#!/usr/bin/env python3
"""
Complete Tech Stack Recommendation Service
Consolidated service that includes all essential functionality:
- AI-powered tech stack recommendations
- Claude API integration
- Feature extraction
- Neo4j knowledge graph operations
- Database operations
"""
import os
import sys
import json
import asyncio
import asyncpg
from datetime import datetime
from typing import Dict, List, Any, Optional
from fastapi import FastAPI, HTTPException
from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel, Field
import uvicorn
from loguru import logger
import anthropic
import requests
from neo4j import AsyncGraphDatabase
# Configure logging
logger.remove()
# Check if running as command line tool
if len(sys.argv) > 2 and sys.argv[1] == "--template-id":
# For command line usage, output logs to stderr
logger.add(lambda msg: print(msg, end="", file=sys.stderr), level="ERROR", format="{time} | {level} | {message}")
else:
# For server usage, output logs to stdout
logger.add(lambda msg: print(msg, end=""), level="INFO", format="{time} | {level} | {message}")
# ============================================================================
# PYDANTIC MODELS
# ============================================================================
class TechRecommendationRequest(BaseModel):
template_id: str = Field(..., description="Template ID to get recommendations for")
class TechRecommendationResponse(BaseModel):
template_id: str
stack_name: str
monthly_cost: float
setup_cost: float
team_size: str
development_time: int
satisfaction: int
success_rate: int
frontend: str
backend: str
database: str
cloud: str
testing: str
mobile: str
devops: str
ai_ml: str
# Single recommended tool
recommended_tool: str = ""
recommendation_score: float
created_at: datetime
# ============================================================================
# CLAUDE CLIENT
# ============================================================================
class ClaudeClient:
"""Claude API client for tech stack recommendations"""
def __init__(self):
# Claude API configuration
self.api_key = os.getenv("CLAUDE_API_KEY")
if not self.api_key:
logger.warning("CLAUDE_API_KEY environment variable not set - AI features will be limited")
self.client = None
else:
# Initialize Anthropic client
self.client = anthropic.Anthropic(api_key=self.api_key)
# Database configuration with fallback
self.db_config = self._get_db_config()
logger.info("ClaudeClient initialized")
def _get_db_config(self):
"""Get database configuration with fallback options"""
# Try environment variables first
host = os.getenv("POSTGRES_HOST")
if not host:
# Check if running inside Docker (postgres hostname available)
try:
import socket
socket.gethostbyname("postgres")
host = "postgres" # Docker internal network
except socket.gaierror:
# Not in Docker, use localhost
host = "localhost"
return {
"host": host,
"port": int(os.getenv("POSTGRES_PORT", "5432")),
"database": os.getenv("POSTGRES_DB", "dev_pipeline"),
"user": os.getenv("POSTGRES_USER", "pipeline_admin"),
"password": os.getenv("POSTGRES_PASSWORD", "secure_pipeline_2024")
}
async def connect_db(self):
"""Create database connection"""
try:
conn = await asyncpg.connect(**self.db_config)
logger.info("Database connected successfully")
return conn
except Exception as e:
logger.error(f"Database connection failed: {e}")
raise
def create_prompt(self, template_data: Dict[str, Any], keywords: List[str]) -> str:
"""Create a prompt for Claude API"""
prompt = f"""
You are a tech stack recommendation expert. Based on the following template information and extracted keywords, recommend a complete tech stack solution including both technologies and ONE essential business tool.
Template Information:
- Type: {template_data.get('type', 'N/A')}
- Title: {template_data.get('title', 'N/A')}
- Description: {template_data.get('description', 'N/A')}
- Category: {template_data.get('category', 'N/A')}
Extracted Keywords: {', '.join(keywords) if keywords else 'None'}
Please provide a complete tech stack recommendation in the following JSON format. Include realistic cost estimates, team size, development time, success metrics, and ONE relevant business tool.
{{
"stack_name": "MVP Startup Stack",
"monthly_cost": 65.0,
"setup_cost": 850.0,
"team_size": "2-4",
"development_time": 3,
"satisfaction": 85,
"success_rate": 88,
"frontend": "Next.js",
"backend": "Node.js",
"database": "PostgreSQL",
"cloud": "Railway",
"testing": "Jest",
"mobile": "React Native",
"devops": "GitHub Actions",
"ai_ml": "Hugging Face",
"recommended_tool": "Shopify",
"recommendation_score": 96.5
}}
Guidelines:
- Choose technologies that work well together
- Provide realistic cost estimates based on the template complexity
- Estimate development time in months
- Include satisfaction and success rate percentages (0-100)
- Set recommendation_score based on how well the stack fits the requirements (0-100)
- Use modern, popular technologies
- Consider the template's business domain and technical requirements
- Select ONLY ONE tool total that best complements the entire tech stack
- Choose the most appropriate tool for the template's specific needs and industry
- The tool should be the most essential business tool for this particular template
IMPORTANT TOOL SELECTION RULES:
- For E-commerce/Online Store templates: Use Shopify, WooCommerce, or Magento
- For CRM/Customer Management: Use Salesforce, HubSpot, or Zoho CRM
- For Analytics/Data: Use Google Analytics, Mixpanel, or Tableau
- For Payment Processing: Use Stripe, PayPal, or Razorpay
- For Communication/Collaboration: Use Slack, Microsoft Teams, or Discord
- For Project Management: Use Trello, Jira, or Asana
- For Marketing: Use Mailchimp, SendGrid, or Constant Contact
- For Social Media: Use Hootsuite, Buffer, or Sprout Social
- For AI/ML projects: Use TensorFlow, PyTorch, or Hugging Face
- For Mobile Apps: Use Firebase, AWS Amplify, or App Store Connect
- For Enterprise: Use Microsoft 365, Google Workspace, or Atlassian
- For Startups: Use Notion, Airtable, or Zapier
Choose the tool that BEST matches the template's primary business function and industry.
Provide only the JSON response, no additional text.
"""
return prompt
async def get_recommendation(self, template_id: str) -> Dict[str, Any]:
"""Get tech stack recommendation from Claude API"""
try:
if not self.client:
raise HTTPException(status_code=503, detail="Claude API not available - API key not configured")
conn = await self.connect_db()
# Get template data - check both templates and custom_templates tables
template_query = """
SELECT id, type, title, description, category
FROM templates
WHERE id = $1
"""
template_result = await conn.fetchrow(template_query, template_id)
if not template_result:
# Try custom_templates table
template_query = """
SELECT id, type, title, description, category
FROM custom_templates
WHERE id = $1
"""
template_result = await conn.fetchrow(template_query, template_id)
if not template_result:
await conn.close()
raise HTTPException(status_code=404, detail="Template not found")
template_data = dict(template_result)
# Get extracted keywords
keywords_result = await conn.fetchrow('''
SELECT keywords_json FROM extracted_keywords
WHERE template_id = $1 AND keywords_json IS NOT NULL
ORDER BY created_at DESC
LIMIT 1
''', template_id)
keywords = []
if keywords_result:
keywords = json.loads(keywords_result['keywords_json'])
await conn.close()
# Create prompt with extracted keywords
prompt = self.create_prompt(template_data, keywords)
# Call Claude API
response = self.client.messages.create(
model="claude-3-5-sonnet-20241022",
max_tokens=2000,
temperature=0.7,
messages=[{"role": "user", "content": prompt}]
)
# Parse response
response_text = response.content[0].text.strip()
# Extract JSON from response
if response_text.startswith('```json'):
response_text = response_text[7:-3]
elif response_text.startswith('```'):
response_text = response_text[3:-3]
response_data = json.loads(response_text)
# Store recommendation
await self.store_tech_recommendations(template_id, response_data)
# Auto-migrate new recommendation to Neo4j
try:
await self.auto_migrate_single_recommendation(template_id)
except Exception as e:
logger.warning(f"Auto-migration failed for template {template_id}: {e}")
return response_data
except Exception as e:
logger.error(f"Error getting recommendation: {e}")
raise HTTPException(status_code=500, detail=f"Failed to get recommendation: {str(e)}")
async def store_tech_recommendations(self, template_id: str, response_data: Dict[str, Any]):
"""Store tech recommendations in tech_stack_recommendations table"""
try:
conn = await self.connect_db()
# Clear existing recommendations for this template
await conn.execute(
"DELETE FROM tech_stack_recommendations WHERE template_id = $1",
template_id
)
# Handle fields that could be dict or string
def format_field(field_value):
if isinstance(field_value, dict):
return json.dumps(field_value)
return str(field_value) if field_value is not None else ''
# Handle single tool
def format_tool(tool_value):
if isinstance(tool_value, str):
return tool_value
return ''
# Store the complete tech stack in the proper table
await conn.execute(
"""
INSERT INTO tech_stack_recommendations
(template_id, stack_name, monthly_cost, setup_cost, team_size, development_time,
satisfaction, success_rate, frontend, backend, database, cloud, testing,
mobile, devops, ai_ml, recommended_tool, recommendation_score)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18)
""",
template_id,
response_data.get('stack_name', 'Tech Stack'),
response_data.get('monthly_cost', 0.0),
response_data.get('setup_cost', 0.0),
response_data.get('team_size', '1-2'),
response_data.get('development_time', 1),
response_data.get('satisfaction', 0),
response_data.get('success_rate', 0),
format_field(response_data.get('frontend', '')),
format_field(response_data.get('backend', '')),
format_field(response_data.get('database', '')),
format_field(response_data.get('cloud', '')),
format_field(response_data.get('testing', '')),
format_field(response_data.get('mobile', '')),
format_field(response_data.get('devops', '')),
format_field(response_data.get('ai_ml', '')),
format_tool(response_data.get('recommended_tool', '')),
response_data.get('recommendation_score', 0.0)
)
await conn.close()
logger.info(f"Stored complete tech stack with tools for template {template_id} in tech_stack_recommendations table")
except Exception as e:
logger.error(f"Error storing tech recommendations: {e}")
async def auto_migrate_single_recommendation(self, template_id: str):
"""Auto-migrate a single recommendation from tech_stack_recommendations table to Neo4j"""
try:
logger.info(f"Starting auto-migration for template {template_id}")
conn = await self.connect_db()
# Get recommendation from tech_stack_recommendations table
rec_query = """
SELECT * FROM tech_stack_recommendations
WHERE template_id = $1
ORDER BY created_at DESC LIMIT 1
"""
rec = await conn.fetchrow(rec_query, template_id)
if not rec:
logger.warning(f"No recommendation found in tech_stack_recommendations for template {template_id}")
await conn.close()
return
logger.info(f"Found recommendation: {rec['stack_name']} for template {template_id}")
# Get template data for context - check both templates and custom_templates tables
template_query = """
SELECT id, title, description, category, type
FROM templates
WHERE id = $1
"""
template_result = await conn.fetchrow(template_query, template_id)
if not template_result:
# Try custom_templates table
template_query = """
SELECT id, title, description, category, type
FROM custom_templates
WHERE id = $1
"""
template_result = await conn.fetchrow(template_query, template_id)
if not template_result:
logger.warning(f"Template {template_id} not found in templates or custom_templates tables")
await conn.close()
return
template_data = dict(template_result)
template_data['id'] = str(template_data['id'])
# Get extracted keywords
keywords_result = await conn.fetchrow('''
SELECT keywords_json FROM extracted_keywords
WHERE template_id = $1 AND keywords_json IS NOT NULL
ORDER BY created_at DESC
LIMIT 1
''', template_id)
keywords = []
if keywords_result:
keywords = json.loads(keywords_result['keywords_json'])
await conn.close()
# Create template node in Neo4j
await neo4j_client.create_template_node(template_data)
# Create tech stack node
tech_stack_data = {
"name": rec['stack_name'],
"category": "tech_stack",
"maturity_score": 0.9,
"learning_curve": "medium",
"performance_rating": float(rec['recommendation_score']) / 100.0
}
await neo4j_client.create_technology_node(tech_stack_data)
# Create recommendation relationship
await neo4j_client.create_recommendation_relationship(
str(template_id),
rec['stack_name'],
"tech_stack",
float(rec['recommendation_score']) / 100.0
)
# Create individual technology nodes and relationships
tech_fields = ['frontend', 'backend', 'database', 'cloud', 'testing', 'mobile', 'devops', 'ai_ml']
for field in tech_fields:
tech_value = rec[field]
if tech_value and tech_value.strip():
# Parse JSON if it's a string
if isinstance(tech_value, str) and tech_value.startswith('{'):
try:
tech_value = json.loads(tech_value)
if isinstance(tech_value, dict):
tech_name = tech_value.get('name', str(tech_value))
else:
tech_name = str(tech_value)
except:
tech_name = str(tech_value)
else:
tech_name = str(tech_value)
# Create technology node
tech_data = {
"name": tech_name,
"category": field,
"maturity_score": 0.8,
"learning_curve": "medium",
"performance_rating": 0.8
}
await neo4j_client.create_technology_node(tech_data)
# Create relationship
await neo4j_client.create_recommendation_relationship(
str(template_id),
tech_name,
field,
0.8
)
# Create tool node for single recommended tool
recommended_tool = rec.get('recommended_tool', '')
if recommended_tool and recommended_tool.strip():
# Create tool node
tool_data = {
"name": recommended_tool,
"category": "business_tool",
"type": "Tool",
"maturity_score": 0.8,
"learning_curve": "easy",
"performance_rating": 0.8
}
await neo4j_client.create_technology_node(tool_data)
# Create relationship
await neo4j_client.create_recommendation_relationship(
str(template_id),
recommended_tool,
"business_tool",
0.8
)
# Create keyword relationships
if keywords and len(keywords) > 0:
logger.info(f"Creating {len(keywords)} keyword relationships for template {template_id}")
for keyword in keywords:
if keyword and keyword.strip():
await neo4j_client.create_keyword_relationship(str(template_id), keyword)
else:
logger.warning(f"No keywords found for template {template_id}, skipping keyword relationships")
# Create TemplateRecommendation node with rich data
recommendation_data = {
'stack_name': rec['stack_name'],
'description': template_data.get('description', ''),
'project_scale': 'medium',
'team_size': 3,
'experience_level': 'intermediate',
'confidence_score': int(rec['recommendation_score']),
'recommendation_reasons': [
f"Tech stack: {rec['stack_name']}",
f"Score: {rec['recommendation_score']}/100",
"AI-generated recommendation"
],
'key_features': [
f"Frontend: {rec.get('frontend', 'N/A')}",
f"Backend: {rec.get('backend', 'N/A')}",
f"Database: {rec.get('database', 'N/A')}",
f"Cloud: {rec.get('cloud', 'N/A')}"
],
'estimated_development_time_months': rec.get('development_time', 3),
'complexity_level': 'medium',
'budget_range_usd': f"${rec.get('monthly_cost', 0):.0f} - ${rec.get('setup_cost', 0):.0f}",
'time_to_market_weeks': rec.get('development_time', 3) * 4,
'scalability_requirements': 'moderate',
'security_requirements': 'standard',
'success_rate_percentage': rec.get('success_rate', 85),
'user_satisfaction_score': rec.get('satisfaction', 85)
}
await neo4j_client.create_template_recommendation_node(str(template_id), recommendation_data)
# Create HAS_RECOMMENDATION relationship between Template and TemplateRecommendation
await neo4j_client.create_has_recommendation_relationship(str(template_id), f"rec-{template_id}")
logger.info(f"✅ Successfully auto-migrated template {template_id} to Neo4j knowledge graph")
except Exception as e:
logger.error(f"Error in auto-migration for template {template_id}: {e}")
# ============================================================================
# FEATURE EXTRACTOR
# ============================================================================
class FeatureExtractor:
"""Extracts features from templates and gets tech stack recommendations"""
def __init__(self):
# Database configurations with fallback
self.template_db_config = self._get_db_config()
# Claude API configuration
self.claude_api_key = os.getenv("CLAUDE_API_KEY")
if not self.claude_api_key:
logger.warning("CLAUDE_API_KEY not set - AI features will be limited")
self.claude_client = anthropic.Anthropic(api_key=self.claude_api_key) if self.claude_api_key else None
logger.info("FeatureExtractor initialized")
def _get_db_config(self):
"""Get database configuration with fallback options"""
# Try environment variables first
host = os.getenv("POSTGRES_HOST")
if not host:
# Check if running inside Docker (postgres hostname available)
try:
import socket
socket.gethostbyname("postgres")
host = "postgres" # Docker internal network
except socket.gaierror:
# Not in Docker, use localhost
host = "localhost"
return {
"host": host,
"port": int(os.getenv("POSTGRES_PORT", "5432")),
"database": os.getenv("POSTGRES_DB", "dev_pipeline"),
"user": os.getenv("POSTGRES_USER", "pipeline_admin"),
"password": os.getenv("POSTGRES_PASSWORD", "secure_pipeline_2024")
}
async def connect_db(self):
"""Create database connection"""
try:
conn = await asyncpg.connect(**self.template_db_config)
logger.info("Database connected successfully")
return conn
except Exception as e:
logger.error(f"Database connection failed: {e}")
raise
async def extract_keywords_from_template(self, template_data: Dict[str, Any]) -> List[str]:
"""Extract keywords from template using local NLP processing"""
try:
# Combine all text data
text_content = f"{template_data.get('title', '')} {template_data.get('description', '')} {template_data.get('category', '')}"
# Clean and process text
keywords = self._extract_keywords_local(text_content)
logger.info(f"Extracted {len(keywords)} keywords locally: {keywords}")
return keywords
except Exception as e:
logger.error(f"Error extracting keywords: {e}")
return []
def _extract_keywords_local(self, text: str) -> List[str]:
"""Extract keywords using local text processing"""
import re
from collections import Counter
# Define technical and business keywords
tech_keywords = {
'web', 'api', 'database', 'frontend', 'backend', 'mobile', 'cloud', 'ai', 'ml', 'analytics',
'ecommerce', 'e-commerce', 'payment', 'authentication', 'security', 'testing', 'deployment',
'microservices', 'rest', 'graphql', 'react', 'angular', 'vue', 'node', 'python', 'java',
'javascript', 'typescript', 'docker', 'kubernetes', 'aws', 'azure', 'gcp', 'postgresql',
'mysql', 'mongodb', 'redis', 'elasticsearch', 'rabbitmq', 'kafka', 'nginx', 'jenkins',
'gitlab', 'github', 'ci', 'cd', 'devops', 'monitoring', 'logging', 'caching', 'scaling'
}
business_keywords = {
'healthcare', 'medical', 'patient', 'appointment', 'records', 'telehealth', 'pharmacy',
'finance', 'banking', 'payment', 'invoice', 'accounting', 'trading', 'investment',
'education', 'learning', 'student', 'course', 'training', 'certification', 'lms',
'retail', 'inventory', 'shopping', 'cart', 'checkout', 'order', 'shipping', 'warehouse',
'crm', 'sales', 'marketing', 'lead', 'customer', 'support', 'ticket', 'workflow',
'automation', 'process', 'approval', 'document', 'file', 'content', 'management',
'enterprise', 'business', 'solution', 'platform', 'service', 'application', 'system'
}
# Clean text
text = re.sub(r'[^\w\s-]', ' ', text.lower())
words = re.findall(r'\b\w+\b', text)
# Filter out common stop words
stop_words = {
'the', 'a', 'an', 'and', 'or', 'but', 'in', 'on', 'at', 'to', 'for', 'of', 'with',
'by', 'from', 'up', 'about', 'into', 'through', 'during', 'before', 'after',
'above', 'below', 'between', 'among', 'is', 'are', 'was', 'were', 'be', 'been',
'being', 'have', 'has', 'had', 'do', 'does', 'did', 'will', 'would', 'could',
'should', 'may', 'might', 'must', 'can', 'this', 'that', 'these', 'those',
'i', 'you', 'he', 'she', 'it', 'we', 'they', 'me', 'him', 'her', 'us', 'them'
}
# Filter words
filtered_words = [word for word in words if len(word) > 2 and word not in stop_words]
# Count word frequency
word_counts = Counter(filtered_words)
# Extract relevant keywords
keywords = []
# Add technical keywords found in text
for word in filtered_words:
if word in tech_keywords or word in business_keywords:
keywords.append(word)
# Add most frequent meaningful words (excluding already added keywords)
remaining_words = [word for word, count in word_counts.most_common(10)
if word not in keywords and count > 1]
keywords.extend(remaining_words[:5])
# Remove duplicates and limit to 10 keywords
unique_keywords = list(dict.fromkeys(keywords))[:10]
return unique_keywords
async def get_template_data(self, template_id: str) -> Optional[Dict[str, Any]]:
"""Get template data from database"""
try:
conn = await self.connect_db()
# Try templates table first
template = await conn.fetchrow(
"""
SELECT id, title, description, category, type
FROM templates
WHERE id = $1
""",
template_id
)
if not template:
# Try custom_templates table
template = await conn.fetchrow(
"""
SELECT id, title, description, category, type
FROM custom_templates
WHERE id = $1
""",
template_id
)
await conn.close()
if template:
return dict(template)
return None
except Exception as e:
logger.error(f"Error getting template data: {e}")
return None
async def get_all_templates(self) -> List[Dict[str, Any]]:
"""Get all templates from both tables"""
try:
conn = await self.connect_db()
# Get from templates table
templates = await conn.fetch(
"""
SELECT id, title, description, category, type
FROM templates
WHERE type NOT IN ('_system', '_migration', '_test')
"""
)
# Get from custom_templates table
custom_templates = await conn.fetch(
"""
SELECT id, title, description, category, type
FROM custom_templates
"""
)
await conn.close()
# Combine results
all_templates = []
for template in templates:
all_templates.append(dict(template))
for template in custom_templates:
all_templates.append(dict(template))
return all_templates
except Exception as e:
logger.error(f"Error getting all templates: {e}")
return []
async def store_extracted_keywords(self, template_id: str, keywords: List[str]):
"""Store extracted keywords in database"""
try:
conn = await self.connect_db()
# Determine template source
template_source = 'templates'
template = await conn.fetchrow("SELECT id FROM templates WHERE id = $1", template_id)
if not template:
template_source = 'custom_templates'
# Store keywords
await conn.execute(
"""
INSERT INTO extracted_keywords (template_id, template_source, keywords_json, created_at)
VALUES ($1, $2, $3, $4)
ON CONFLICT (template_id, template_source)
DO UPDATE SET keywords_json = $3, updated_at = $4
""",
template_id,
template_source,
json.dumps(keywords),
datetime.now()
)
await conn.close()
logger.info(f"Stored keywords for template {template_id} from {template_source}")
except Exception as e:
logger.error(f"Error storing extracted keywords: {e}")
async def store_keywords(self, template_id: str, keywords: List[str]):
"""Store extracted keywords in database"""
try:
conn = await self.connect_db()
# Store keywords
await conn.execute(
"""
INSERT INTO extracted_keywords (template_id, keywords_json, created_at)
VALUES ($1, $2, $3)
ON CONFLICT (template_id)
DO UPDATE SET keywords_json = $2, updated_at = $3
""",
template_id,
json.dumps(keywords),
datetime.now()
)
await conn.close()
logger.info(f"Stored keywords for template {template_id}")
except Exception as e:
logger.error(f"Error storing keywords: {e}")
# ============================================================================
# NEO4J CLIENT
# ============================================================================
class Neo4jClient:
"""Neo4j client for knowledge graph operations"""
def __init__(self):
# Neo4j configuration - try multiple connection options
self.uri = self._get_neo4j_uri()
self.username = os.getenv("NEO4J_USERNAME", "neo4j")
self.password = os.getenv("NEO4J_PASSWORD", "password")
# Create driver
self._create_driver()
def _get_neo4j_uri(self):
"""Get Neo4j URI with fallback options"""
# Try environment variable first
uri = os.getenv("NEO4J_URI")
if uri:
return uri
# Check if running inside Docker (neo4j hostname available)
try:
import socket
socket.gethostbyname("neo4j")
return "bolt://neo4j:7687" # Docker internal network
except socket.gaierror:
# Not in Docker, use localhost
return "bolt://localhost:7687"
def _create_driver(self):
"""Create Neo4j driver"""
self.driver = AsyncGraphDatabase.driver(
self.uri,
auth=(self.username, self.password)
)
logger.info(f"Neo4jClient initialized with URI: {self.uri}")
async def close(self):
"""Close the Neo4j driver"""
await self.driver.close()
logger.info("Neo4j connection closed")
async def test_connection(self):
"""Test Neo4j connection"""
try:
async with self.driver.session() as session:
result = await session.run("RETURN 1 as test")
record = await result.single()
if record and record["test"] == 1:
logger.info("Neo4j connection successful")
return True
else:
logger.error("Neo4j connection test failed")
return False
except Exception as e:
logger.error(f"Neo4j connection failed: {e}")
return False
async def create_constraints(self):
"""Create Neo4j constraints"""
try:
async with self.driver.session() as session:
# Create constraints
constraints = [
"CREATE CONSTRAINT template_id_unique IF NOT EXISTS FOR (t:Template) REQUIRE t.id IS UNIQUE",
"CREATE CONSTRAINT technology_name_unique IF NOT EXISTS FOR (tech:Technology) REQUIRE tech.name IS UNIQUE",
"CREATE CONSTRAINT keyword_name_unique IF NOT EXISTS FOR (k:Keyword) REQUIRE k.name IS UNIQUE"
]
for constraint in constraints:
try:
await session.run(constraint)
except Exception as e:
logger.warning(f"Constraint creation warning: {e}")
logger.info("Neo4j constraints created successfully")
except Exception as e:
logger.error(f"Error creating constraints: {e}")
async def create_template_node(self, template_data: Dict[str, Any]):
"""Create or update template node"""
try:
async with self.driver.session() as session:
await session.run(
"""
MERGE (t:Template {id: $id})
SET t.name = $name,
t.description = $description,
t.category = $category,
t.type = $type,
t.updated_at = datetime()
""",
id=template_data.get('id'),
name=template_data.get('name', template_data.get('title', '')),
description=template_data.get('description', ''),
category=template_data.get('category', ''),
type=template_data.get('type', '')
)
logger.info(f"Created/updated template node: {template_data.get('name', template_data.get('title', ''))}")
except Exception as e:
logger.error(f"Error creating template node: {e}")
async def create_technology_node(self, tech_data: Dict[str, Any]):
"""Create or update technology node"""
try:
async with self.driver.session() as session:
await session.run(
"""
MERGE (tech:Technology {name: $name})
SET tech.category = $category,
tech.type = $type,
tech.maturity_score = $maturity_score,
tech.learning_curve = $learning_curve,
tech.performance_rating = $performance_rating,
tech.updated_at = datetime()
""",
name=tech_data.get('name'),
category=tech_data.get('category', ''),
type=tech_data.get('type', 'Technology'),
maturity_score=tech_data.get('maturity_score', 0.8),
learning_curve=tech_data.get('learning_curve', 'medium'),
performance_rating=tech_data.get('performance_rating', 0.8)
)
logger.info(f"Created/updated technology node: {tech_data.get('name')}")
except Exception as e:
logger.error(f"Error creating technology node: {e}")
async def create_recommendation_relationship(self, template_id: str, tech_name: str, category: str, score: float):
"""Create recommendation relationship"""
try:
async with self.driver.session() as session:
await session.run(
"""
MATCH (t:Template {id: $template_id})
MATCH (tech:Technology {name: $tech_name})
MERGE (t)-[r:RECOMMENDED_TECHNOLOGY {category: $category, score: $score}]->(tech)
SET r.updated_at = datetime()
""",
template_id=template_id,
tech_name=tech_name,
category=category,
score=score
)
logger.info(f"Created recommendation relationship: {template_id} -> {tech_name}")
except Exception as e:
logger.error(f"Error creating recommendation relationship: {e}")
async def create_keyword_relationship(self, template_id: str, keyword: str):
"""Create keyword relationship"""
try:
async with self.driver.session() as session:
# Create keyword node
await session.run(
"""
MERGE (k:Keyword {name: $keyword})
SET k.updated_at = datetime()
""",
keyword=keyword
)
# Create relationship
await session.run(
"""
MATCH (t:Template {id: $template_id})
MATCH (k:Keyword {name: $keyword})
MERGE (t)-[r:HAS_KEYWORD]->(k)
SET r.updated_at = datetime()
""",
template_id=template_id,
keyword=keyword
)
logger.info(f"Created keyword relationship: {template_id} -> {keyword}")
except Exception as e:
logger.error(f"Error creating keyword relationship: {e}")
async def create_has_recommendation_relationship(self, template_id: str, recommendation_id: str):
"""Create HAS_RECOMMENDATION relationship between Template and TemplateRecommendation"""
try:
async with self.driver.session() as session:
await session.run(
"""
MATCH (t:Template {id: $template_id})
MATCH (tr:TemplateRecommendation {id: $recommendation_id})
MERGE (t)-[r:HAS_RECOMMENDATION]->(tr)
SET r.created_at = datetime(),
r.updated_at = datetime()
""",
template_id=template_id,
recommendation_id=recommendation_id
)
logger.info(f"Created HAS_RECOMMENDATION relationship: {template_id} -> {recommendation_id}")
except Exception as e:
logger.error(f"Error creating HAS_RECOMMENDATION relationship: {e}")
async def get_recommendations_from_neo4j(self, template_id: str) -> Optional[Dict[str, Any]]:
"""Get tech stack recommendations from Neo4j knowledge graph"""
try:
# Convert UUID to string if needed
template_id_str = str(template_id)
async with self.driver.session() as session:
# Query for template recommendations from Neo4j
result = await session.run(
"""
MATCH (t:Template {id: $template_id})-[:HAS_RECOMMENDATION]->(tr:TemplateRecommendation)
OPTIONAL MATCH (t)-[r:RECOMMENDED_TECHNOLOGY]->(tech:Technology)
WITH tr, collect({
name: tech.name,
category: r.category,
score: r.score,
type: tech.type,
maturity_score: tech.maturity_score,
learning_curve: tech.learning_curve,
performance_rating: tech.performance_rating
}) as technologies
RETURN tr.business_domain as business_domain,
tr.project_type as project_type,
tr.team_size as team_size,
tr.confidence_score as confidence_score,
tr.estimated_development_time_months as development_time,
tr.success_rate_percentage as success_rate,
tr.user_satisfaction_score as satisfaction,
tr.budget_range_usd as budget_range,
tr.complexity_level as complexity_level,
technologies
ORDER BY tr.created_at DESC
LIMIT 1
""",
template_id=template_id_str
)
record = await result.single()
if record:
# Process technologies by category
tech_categories = {}
for tech in record['technologies']:
category = tech['category']
if category not in tech_categories:
tech_categories[category] = []
tech_categories[category].append(tech)
# Build recommendation response
recommendation = {
'stack_name': f"{record['business_domain']} {record['project_type']} Stack",
'monthly_cost': record['budget_range'] / 12 if record['budget_range'] else 1000,
'setup_cost': record['budget_range'] if record['budget_range'] else 5000,
'team_size': record['team_size'] or '2-4',
'development_time': record['development_time'] or 6,
'satisfaction': record['satisfaction'] or 85,
'success_rate': record['success_rate'] or 80,
'frontend': '',
'backend': '',
'database': '',
'cloud': '',
'testing': '',
'mobile': '',
'devops': '',
'ai_ml': '',
'recommended_tool': '',
'recommendation_score': record['confidence_score'] or 85.0
}
# Map technologies to categories
for category, techs in tech_categories.items():
if techs:
best_tech = max(techs, key=lambda x: x['score'])
if category.lower() == 'frontend':
recommendation['frontend'] = best_tech['name']
elif category.lower() == 'backend':
recommendation['backend'] = best_tech['name']
elif category.lower() == 'database':
recommendation['database'] = best_tech['name']
elif category.lower() == 'cloud':
recommendation['cloud'] = best_tech['name']
elif category.lower() == 'testing':
recommendation['testing'] = best_tech['name']
elif category.lower() == 'mobile':
recommendation['mobile'] = best_tech['name']
elif category.lower() == 'devops':
recommendation['devops'] = best_tech['name']
elif category.lower() in ['ai', 'ml', 'ai_ml']:
recommendation['ai_ml'] = best_tech['name']
elif category.lower() == 'tool':
recommendation['recommended_tool'] = best_tech['name']
logger.info(f"Found recommendations in Neo4j for template {template_id}: {recommendation['stack_name']}")
return recommendation
else:
logger.info(f"No recommendations found in Neo4j for template {template_id}")
return None
except Exception as e:
logger.error(f"Error getting recommendations from Neo4j: {e}")
return None
async def create_template_recommendation_node(self, template_id: str, recommendation_data: Dict[str, Any]):
"""Create TemplateRecommendation node with rich data"""
try:
async with self.driver.session() as session:
# Extract business domain from template category or description
business_domain = self._extract_business_domain(recommendation_data)
project_type = self._extract_project_type(recommendation_data)
# Create TemplateRecommendation node
await session.run(
"""
MERGE (tr:TemplateRecommendation {id: $id})
SET tr.business_domain = $business_domain,
tr.project_type = $project_type,
tr.project_scale = $project_scale,
tr.team_size = $team_size,
tr.experience_level = $experience_level,
tr.confidence_score = $confidence_score,
tr.recommendation_reasons = $recommendation_reasons,
tr.key_features = $key_features,
tr.estimated_development_time_months = $estimated_development_time_months,
tr.complexity_level = $complexity_level,
tr.budget_range_usd = $budget_range_usd,
tr.time_to_market_weeks = $time_to_market_weeks,
tr.scalability_requirements = $scalability_requirements,
tr.security_requirements = $security_requirements,
tr.success_rate_percentage = $success_rate_percentage,
tr.user_satisfaction_score = $user_satisfaction_score,
tr.created_by_system = $created_by_system,
tr.recommendation_source = $recommendation_source,
tr.is_active = $is_active,
tr.usage_count = $usage_count,
tr.created_at = datetime(),
tr.updated_at = datetime()
""",
id=f"rec-{template_id}",
business_domain=business_domain,
project_type=project_type,
project_scale=recommendation_data.get('project_scale', 'medium'),
team_size=recommendation_data.get('team_size', 3),
experience_level=recommendation_data.get('experience_level', 'intermediate'),
confidence_score=recommendation_data.get('confidence_score', 85),
recommendation_reasons=recommendation_data.get('recommendation_reasons', ['AI-generated recommendation']),
key_features=recommendation_data.get('key_features', []),
estimated_development_time_months=recommendation_data.get('estimated_development_time_months', 3),
complexity_level=recommendation_data.get('complexity_level', 'medium'),
budget_range_usd=recommendation_data.get('budget_range_usd', '$5,000 - $15,000'),
time_to_market_weeks=recommendation_data.get('time_to_market_weeks', 12),
scalability_requirements=recommendation_data.get('scalability_requirements', 'moderate'),
security_requirements=recommendation_data.get('security_requirements', 'standard'),
success_rate_percentage=recommendation_data.get('success_rate_percentage', 85),
user_satisfaction_score=recommendation_data.get('user_satisfaction_score', 85),
created_by_system=True,
recommendation_source='ai_model',
is_active=True,
usage_count=0
)
# Create relationship from Template to TemplateRecommendation
await session.run(
"""
MATCH (t:Template {id: $template_id})
MATCH (tr:TemplateRecommendation {id: $rec_id})
MERGE (t)-[:RECOMMENDED_FOR]->(tr)
""",
template_id=template_id,
rec_id=f"rec-{template_id}"
)
logger.info(f"Created TemplateRecommendation node: rec-{template_id}")
except Exception as e:
logger.error(f"Error creating TemplateRecommendation node: {e}")
def _extract_business_domain(self, recommendation_data: Dict[str, Any]) -> str:
"""Extract business domain from recommendation data"""
# Try to extract from stack name or description
stack_name = recommendation_data.get('stack_name', '').lower()
description = recommendation_data.get('description', '').lower()
if any(word in stack_name or word in description for word in ['ecommerce', 'e-commerce', 'shop', 'store', 'retail']):
return 'E-commerce'
elif any(word in stack_name or word in description for word in ['social', 'community', 'network']):
return 'Social Media'
elif any(word in stack_name or word in description for word in ['finance', 'payment', 'banking', 'fintech']):
return 'Fintech'
elif any(word in stack_name or word in description for word in ['health', 'medical', 'care']):
return 'Healthcare'
elif any(word in stack_name or word in description for word in ['education', 'learning', 'course']):
return 'Education'
else:
return 'General Business'
def _extract_project_type(self, recommendation_data: Dict[str, Any]) -> str:
"""Extract project type from recommendation data"""
stack_name = recommendation_data.get('stack_name', '').lower()
description = recommendation_data.get('description', '').lower()
if any(word in stack_name or word in description for word in ['web', 'website', 'portal']):
return 'Web Application'
elif any(word in stack_name or word in description for word in ['mobile', 'app', 'ios', 'android']):
return 'Mobile Application'
elif any(word in stack_name or word in description for word in ['api', 'service', 'microservice']):
return 'API Service'
elif any(word in stack_name or word in description for word in ['dashboard', 'admin', 'management']):
return 'Management Dashboard'
else:
return 'Web Application'
# ============================================================================
# FASTAPI APPLICATION
# ============================================================================
# Initialize FastAPI app
app = FastAPI(
title="Tech Stack Recommendation Service",
description="AI-powered tech stack recommendations with tools integration",
version="1.0.0"
)
# Add CORS middleware
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# Initialize clients
claude_client = ClaudeClient()
feature_extractor = FeatureExtractor()
neo4j_client = Neo4jClient()
@app.on_event("startup")
async def startup_event():
"""Initialize services on startup"""
print("🚀 STARTING TECH STACK RECOMMENDATION SERVICE")
print("=" * 50)
print("✅ AI Service will be available at: http://localhost:8013")
print("✅ API Documentation: http://localhost:8013/docs")
print("✅ Test endpoint: POST http://localhost:8013/ai/recommendations")
print("=" * 50)
# Automatic migration on startup
print("🔄 Starting automatic migration to Neo4j...")
try:
await migrate_to_neo4j()
print("✅ Automatic migration completed successfully!")
except Exception as e:
print(f"⚠️ Migration warning: {e}")
print("✅ Service will continue running with existing data")
print("=" * 50)
@app.get("/")
async def root():
"""Root endpoint"""
return {
"message": "Tech Stack Recommendation Service",
"version": "1.0.0",
"status": "running",
"endpoints": {
"recommendations": "POST /ai/recommendations",
"docs": "GET /docs"
}
}
@app.get("/health")
async def health_check():
"""Health check endpoint"""
return {"status": "healthy", "timestamp": datetime.now()}
@app.post("/ai/recommendations/formatted")
async def get_formatted_tech_recommendations(request: TechRecommendationRequest):
"""Get tech stack recommendations in a formatted, user-friendly way"""
try:
logger.info(f"Getting formatted recommendations for template: {request.template_id}")
# Get the standard recommendation
conn = await claude_client.connect_db()
recommendations = await conn.fetch('''
SELECT template_id, stack_name, monthly_cost, setup_cost, team_size,
development_time, satisfaction, success_rate, frontend, backend,
database, cloud, testing, mobile, devops, ai_ml, recommended_tool,
recommendation_score, created_at, updated_at
FROM tech_stack_recommendations
WHERE template_id = $1
ORDER BY created_at DESC
LIMIT 1
''', request.template_id)
if recommendations:
rec = dict(recommendations[0])
await conn.close()
# Format the response in a user-friendly way
formatted_response = {
"template_id": request.template_id,
"tech_stack": {
"name": rec.get('stack_name', 'Tech Stack'),
"score": f"{rec.get('recommendation_score', 0.0)}/100",
"technologies": {
"Frontend": rec.get('frontend', ''),
"Backend": rec.get('backend', ''),
"Database": rec.get('database', ''),
"Cloud": rec.get('cloud', ''),
"Testing": rec.get('testing', ''),
"Mobile": rec.get('mobile', ''),
"DevOps": rec.get('devops', ''),
"AI/ML": rec.get('ai_ml', '')
},
"recommended_tool": rec.get('recommended_tool', ''),
"costs": {
"monthly": f"${rec.get('monthly_cost', 0.0)}",
"setup": f"${rec.get('setup_cost', 0.0)}"
},
"team": {
"size": rec.get('team_size', '1-2'),
"development_time": f"{rec.get('development_time', 1)} months"
},
"metrics": {
"satisfaction": f"{rec.get('satisfaction', 0)}%",
"success_rate": f"{rec.get('success_rate', 0)}%"
}
},
"created_at": rec.get('created_at', datetime.now())
}
return formatted_response
else:
await conn.close()
return {"error": "No recommendations found for this template"}
except Exception as e:
logger.error(f"Error getting formatted recommendations: {e}")
raise HTTPException(status_code=500, detail=str(e))
@app.get("/extract-keywords/{template_id}")
async def get_extracted_keywords(template_id: str):
"""Get extracted keywords for a specific template"""
try:
logger.info(f"Getting keywords for template: {template_id}")
conn = await feature_extractor.connect_db()
# Get keywords from database
keywords_result = await conn.fetchrow('''
SELECT keywords_json, created_at, template_source
FROM extracted_keywords
WHERE template_id = $1 AND keywords_json IS NOT NULL
ORDER BY created_at DESC
LIMIT 1
''', template_id)
await conn.close()
if not keywords_result:
raise HTTPException(status_code=404, detail="No keywords found for this template")
keywords = json.loads(keywords_result['keywords_json']) if keywords_result['keywords_json'] else []
return {
"template_id": template_id,
"keywords": keywords,
"count": len(keywords),
"created_at": keywords_result['created_at'],
"template_source": keywords_result['template_source']
}
except Exception as e:
logger.error(f"Error getting keywords: {e}")
raise HTTPException(status_code=500, detail=str(e))
@app.post("/extract-keywords/{template_id}")
async def extract_keywords_for_template(template_id: str):
"""Extract keywords for a specific template"""
try:
logger.info(f"Extracting keywords for template: {template_id}")
# Get template data from database
template_data = await feature_extractor.get_template_data(template_id)
if not template_data:
raise HTTPException(status_code=404, detail="Template not found")
# Extract keywords using local NLP
keywords = await feature_extractor.extract_keywords_from_template(template_data)
# Store keywords in database
await feature_extractor.store_extracted_keywords(template_id, keywords)
return {
"template_id": template_id,
"keywords": keywords,
"count": len(keywords)
}
except Exception as e:
logger.error(f"Error extracting keywords: {e}")
raise HTTPException(status_code=500, detail=str(e))
@app.post("/extract-keywords-all")
async def extract_keywords_for_all_templates():
"""Extract keywords for all templates"""
try:
logger.info("Extracting keywords for all templates")
# Get all templates from database
templates = await feature_extractor.get_all_templates()
results = []
for template in templates:
try:
# Extract keywords using Claude AI
keywords = await feature_extractor.extract_keywords_from_template(template)
# Store keywords in database
await feature_extractor.store_extracted_keywords(template['id'], keywords)
results.append({
"template_id": template['id'],
"title": template['title'],
"keywords": keywords,
"count": len(keywords)
})
except Exception as e:
logger.error(f"Error extracting keywords for template {template['id']}: {e}")
results.append({
"template_id": template['id'],
"title": template['title'],
"error": str(e)
})
return {
"total_templates": len(templates),
"processed": len(results),
"results": results
}
except Exception as e:
logger.error(f"Error in bulk keyword extraction: {e}")
raise HTTPException(status_code=500, detail=str(e))
@app.post("/auto-workflow/{template_id}")
async def trigger_automatic_workflow(template_id: str):
"""Trigger complete automatic workflow for a new template"""
try:
logger.info(f"🚀 Starting automatic workflow for template: {template_id}")
# Step 1: Extract keywords
logger.info("📝 Step 1: Extracting keywords...")
template_data = await feature_extractor.get_template_data(template_id)
if not template_data:
raise HTTPException(status_code=404, detail="Template not found")
keywords = await feature_extractor.extract_keywords_from_template(template_data)
await feature_extractor.store_extracted_keywords(template_id, keywords)
logger.info(f"✅ Keywords extracted and stored: {len(keywords)} keywords")
# Step 2: Generate tech stack recommendation
logger.info("🤖 Step 2: Generating tech stack recommendation...")
try:
recommendation_data = await claude_client.get_recommendation(template_id)
logger.info(f"✅ Tech stack recommendation generated: {recommendation_data.get('stack_name', 'Unknown')}")
except Exception as e:
logger.warning(f"⚠️ Claude AI failed (likely billing issue): {e}")
logger.info("🔄 Using database fallback for recommendation...")
# Check if recommendation already exists in database
conn = await claude_client.connect_db()
existing_rec = await conn.fetchrow('''
SELECT * FROM tech_stack_recommendations
WHERE template_id = $1
ORDER BY created_at DESC LIMIT 1
''', template_id)
if existing_rec:
recommendation_data = dict(existing_rec)
logger.info(f"✅ Found existing recommendation: {recommendation_data.get('stack_name', 'Unknown')}")
else:
# Create a basic recommendation
recommendation_data = {
'stack_name': f'{template_data.get("title", "Template")} Tech Stack',
'monthly_cost': 100.0,
'setup_cost': 2000.0,
'team_size': '3-5',
'development_time': 6,
'satisfaction': 85,
'success_rate': 90,
'frontend': 'React.js',
'backend': 'Node.js',
'database': 'PostgreSQL',
'cloud': 'AWS',
'testing': 'Jest',
'mobile': 'React Native',
'devops': 'Docker',
'ai_ml': 'TensorFlow',
'recommended_tool': 'Custom Tool',
'recommendation_score': 85.0
}
logger.info(f"✅ Created basic recommendation: {recommendation_data.get('stack_name', 'Unknown')}")
await conn.close()
# Step 3: Auto-migrate to Neo4j
logger.info("🔄 Step 3: Auto-migrating to Neo4j knowledge graph...")
await claude_client.auto_migrate_single_recommendation(template_id)
logger.info("✅ Auto-migration to Neo4j completed")
return {
"template_id": template_id,
"workflow_status": "completed",
"steps_completed": [
"keyword_extraction",
"tech_stack_recommendation",
"neo4j_migration"
],
"keywords_count": len(keywords),
"stack_name": recommendation_data.get('stack_name', 'Unknown'),
"message": "Complete workflow executed successfully"
}
except Exception as e:
logger.error(f"Error in automatic workflow for template {template_id}: {e}")
raise HTTPException(status_code=500, detail=f"Workflow failed: {str(e)}")
@app.post("/auto-workflow-batch")
async def trigger_automatic_workflow_batch():
"""Trigger automatic workflow for all templates without recommendations"""
try:
logger.info("🚀 Starting batch automatic workflow for all templates")
# Get all templates without recommendations
conn = await claude_client.connect_db()
templates_query = """
SELECT t.id, t.title, t.description, t.category, t.type
FROM templates t
LEFT JOIN tech_stack_recommendations tsr ON t.id = tsr.template_id
WHERE tsr.template_id IS NULL
AND t.type NOT LIKE '_%'
UNION
SELECT ct.id, ct.title, ct.description, ct.category, ct.type
FROM custom_templates ct
LEFT JOIN tech_stack_recommendations tsr ON ct.id = tsr.template_id
WHERE tsr.template_id IS NULL
AND ct.type NOT LIKE '_%'
"""
templates = await conn.fetch(templates_query)
await conn.close()
logger.info(f"📋 Found {len(templates)} templates without recommendations")
results = []
for i, template in enumerate(templates, 1):
try:
logger.info(f"🔄 Processing {i}/{len(templates)}: {template['title']}")
# Trigger workflow for this template
workflow_result = await trigger_automatic_workflow(template['id'])
results.append({
"template_id": template['id'],
"title": template['title'],
"status": "success",
"workflow_result": workflow_result
})
except Exception as e:
logger.error(f"Error processing template {template['id']}: {e}")
results.append({
"template_id": template['id'],
"title": template['title'],
"status": "failed",
"error": str(e)
})
success_count = len([r for r in results if r['status'] == 'success'])
failed_count = len([r for r in results if r['status'] == 'failed'])
return {
"message": f"Batch workflow completed: {success_count} success, {failed_count} failed",
"total_templates": len(templates),
"success_count": success_count,
"failed_count": failed_count,
"results": results
}
except Exception as e:
logger.error(f"Error in batch automatic workflow: {e}")
raise HTTPException(status_code=500, detail=str(e))
@app.post("/ai/recommendations")
async def get_tech_recommendations(request: TechRecommendationRequest):
"""Get tech stack recommendations for a template"""
try:
logger.info(f"Getting recommendations for template: {request.template_id}")
# 1. FIRST: Check Neo4j knowledge graph for recommendations
logger.info("🔍 Checking Neo4j knowledge graph for recommendations...")
neo4j_recommendation = await neo4j_client.get_recommendations_from_neo4j(request.template_id)
if neo4j_recommendation:
logger.info(f"✅ Found recommendations in Neo4j: {neo4j_recommendation['stack_name']}")
# Format the response from Neo4j data
response = TechRecommendationResponse(
template_id=request.template_id,
stack_name=neo4j_recommendation.get('stack_name', 'Tech Stack'),
monthly_cost=float(neo4j_recommendation.get('monthly_cost', 0.0)),
setup_cost=float(neo4j_recommendation.get('setup_cost', 0.0)),
team_size=neo4j_recommendation.get('team_size', '1-2'),
development_time=neo4j_recommendation.get('development_time', 1),
satisfaction=neo4j_recommendation.get('satisfaction', 0),
success_rate=neo4j_recommendation.get('success_rate', 0),
frontend=neo4j_recommendation.get('frontend', ''),
backend=neo4j_recommendation.get('backend', ''),
database=neo4j_recommendation.get('database', ''),
cloud=neo4j_recommendation.get('cloud', ''),
testing=neo4j_recommendation.get('testing', ''),
mobile=neo4j_recommendation.get('mobile', ''),
devops=neo4j_recommendation.get('devops', ''),
ai_ml=neo4j_recommendation.get('ai_ml', ''),
recommended_tool=neo4j_recommendation.get('recommended_tool', ''),
recommendation_score=float(neo4j_recommendation.get('recommendation_score', 0.0)),
created_at=datetime.now()
)
# Log the complete tech stack with tool for visibility
logger.info(f"📋 Complete Tech Stack Recommendation:")
logger.info(f" 🎯 Stack: {response.stack_name}")
logger.info(f" 💻 Frontend: {response.frontend}")
logger.info(f" ⚙️ Backend: {response.backend}")
logger.info(f" 🗄️ Database: {response.database}")
logger.info(f" ☁️ Cloud: {response.cloud}")
logger.info(f" 🧪 Testing: {response.testing}")
logger.info(f" 📱 Mobile: {response.mobile}")
logger.info(f" 🚀 DevOps: {response.devops}")
logger.info(f" 🤖 AI/ML: {response.ai_ml}")
logger.info(f" 🔧 Recommended Tool: {response.recommended_tool}")
logger.info(f" ⭐ Score: {response.recommendation_score}")
# Return in the requested format with recommendations array
return {
"recommendations": [
{
"template_id": response.template_id,
"stack_name": response.stack_name,
"monthly_cost": response.monthly_cost,
"setup_cost": response.setup_cost,
"team_size": response.team_size,
"development_time": response.development_time,
"satisfaction": response.satisfaction,
"success_rate": response.success_rate,
"frontend": response.frontend,
"backend": response.backend,
"database": response.database,
"cloud": response.cloud,
"testing": response.testing,
"mobile": response.mobile,
"devops": response.devops,
"ai_ml": response.ai_ml,
"recommendation_score": response.recommendation_score
}
]
}
else:
# 2. SECOND: Check database as fallback
logger.info("🔍 Neo4j not found, checking database as fallback...")
conn = await claude_client.connect_db()
recommendations = await conn.fetch('''
SELECT template_id, stack_name, monthly_cost, setup_cost, team_size,
development_time, satisfaction, success_rate, frontend, backend,
database, cloud, testing, mobile, devops, ai_ml, recommended_tool,
recommendation_score, created_at, updated_at
FROM tech_stack_recommendations
WHERE template_id = $1
ORDER BY created_at DESC
LIMIT 1
''', request.template_id)
if recommendations:
rec = dict(recommendations[0])
logger.info(f"✅ Found recommendations in database: {rec.get('stack_name', 'Unknown')}")
# Auto-migrate to Neo4j when found in database
try:
logger.info("🔄 Auto-migrating database recommendation to Neo4j...")
await claude_client.auto_migrate_single_recommendation(request.template_id)
except Exception as e:
logger.warning(f"Auto-migration failed for template {request.template_id}: {e}")
await conn.close()
# Format the response from database
response = TechRecommendationResponse(
template_id=request.template_id,
stack_name=rec.get('stack_name', 'Tech Stack'),
monthly_cost=float(rec.get('monthly_cost', 0.0)),
setup_cost=float(rec.get('setup_cost', 0.0)),
team_size=rec.get('team_size', '1-2'),
development_time=rec.get('development_time', 1),
satisfaction=rec.get('satisfaction', 0),
success_rate=rec.get('success_rate', 0),
frontend=rec.get('frontend', ''),
backend=rec.get('backend', ''),
database=rec.get('database', ''),
cloud=rec.get('cloud', ''),
testing=rec.get('testing', ''),
mobile=rec.get('mobile', ''),
devops=rec.get('devops', ''),
ai_ml=rec.get('ai_ml', ''),
recommended_tool=rec.get('recommended_tool', ''),
recommendation_score=float(rec.get('recommendation_score', 0.0)),
created_at=datetime.now()
)
# Log the complete tech stack with tool for visibility
logger.info(f"📋 Complete Tech Stack Recommendation (from database):")
logger.info(f" 🎯 Stack: {response.stack_name}")
logger.info(f" 💻 Frontend: {response.frontend}")
logger.info(f" ⚙️ Backend: {response.backend}")
logger.info(f" 🗄️ Database: {response.database}")
logger.info(f" ☁️ Cloud: {response.cloud}")
logger.info(f" 🧪 Testing: {response.testing}")
logger.info(f" 📱 Mobile: {response.mobile}")
logger.info(f" 🚀 DevOps: {response.devops}")
logger.info(f" 🤖 AI/ML: {response.ai_ml}")
logger.info(f" 🔧 Recommended Tool: {response.recommended_tool}")
logger.info(f" ⭐ Score: {response.recommendation_score}")
# Return in the requested format with recommendations array
return {
"recommendations": [
{
"template_id": response.template_id,
"stack_name": response.stack_name,
"monthly_cost": response.monthly_cost,
"setup_cost": response.setup_cost,
"team_size": response.team_size,
"development_time": response.development_time,
"satisfaction": response.satisfaction,
"success_rate": response.success_rate,
"frontend": response.frontend,
"backend": response.backend,
"database": response.database,
"cloud": response.cloud,
"testing": response.testing,
"mobile": response.mobile,
"devops": response.devops,
"ai_ml": response.ai_ml,
"recommendation_score": response.recommendation_score
}
]
}
else:
# 3. THIRD: Generate new recommendations using Claude AI
logger.info("🔍 No existing recommendations found, generating new ones with Claude AI...")
await conn.close()
response_data = await claude_client.get_recommendation(request.template_id)
# Get keywords
conn = await claude_client.connect_db()
keywords_result = await conn.fetchrow('''
SELECT keywords_json FROM extracted_keywords
WHERE template_id = $1 AND keywords_json IS NOT NULL
ORDER BY template_source
LIMIT 1
''', request.template_id)
keywords = []
if keywords_result:
keywords = json.loads(keywords_result['keywords_json'])
await conn.close()
# Return in the requested format with recommendations array
return {
"recommendations": [
{
"template_id": request.template_id,
"stack_name": response_data.get('stack_name', 'Tech Stack'),
"monthly_cost": float(response_data.get('monthly_cost', 0.0)),
"setup_cost": float(response_data.get('setup_cost', 0.0)),
"team_size": response_data.get('team_size', '1-2'),
"development_time": response_data.get('development_time', 1),
"satisfaction": response_data.get('satisfaction', 0),
"success_rate": response_data.get('success_rate', 0),
"frontend": response_data.get('frontend', ''),
"backend": response_data.get('backend', ''),
"database": response_data.get('database', ''),
"cloud": response_data.get('cloud', ''),
"testing": response_data.get('testing', ''),
"mobile": response_data.get('mobile', ''),
"devops": response_data.get('devops', ''),
"ai_ml": response_data.get('ai_ml', ''),
"recommendation_score": float(response_data.get('recommendation_score', 0.0))
}
]
}
except Exception as e:
logger.error(f"Error getting recommendations: {e}")
raise HTTPException(status_code=500, detail=str(e))
# ============================================================================
# MIGRATION FUNCTIONALITY
# ============================================================================
async def migrate_to_neo4j():
"""Migrate tech stack recommendations to Neo4j knowledge graph"""
print("🚀 Migrating Tech Stack Recommendations to Neo4j Knowledge Graph")
print("=" * 70)
try:
# Test Neo4j connection
if not await neo4j_client.test_connection():
print("❌ Neo4j connection failed")
return
# Create constraints
await neo4j_client.create_constraints()
print("✅ Neo4j constraints created")
# Connect to PostgreSQL
conn = await claude_client.connect_db()
print("✅ PostgreSQL connected")
# Get templates with recommendations
templates_query = """
SELECT DISTINCT t.id, t.title, t.description, t.category, t.type, t.created_at
FROM templates t
JOIN tech_stack_recommendations tsr ON t.id = tsr.template_id
ORDER BY t.created_at DESC
"""
templates = await conn.fetch(templates_query)
print(f"📋 Found {len(templates)} templates to migrate")
for i, template in enumerate(templates, 1):
print(f"\n📝 Processing {i}/{len(templates)}: {template['title']}")
# Get recommendation
rec_query = """
SELECT * FROM tech_stack_recommendations
WHERE template_id = $1
ORDER BY created_at DESC LIMIT 1
"""
rec = await conn.fetchrow(rec_query, template['id'])
if not rec:
print(" ⚠️ No recommendations found for this template")
continue
print(f" 🔍 Found recommendation: {rec['stack_name']}")
# Get keywords for this template
keywords_query = """
SELECT keywords_json FROM extracted_keywords
WHERE template_id = $1 AND template_source = 'templates'
ORDER BY created_at DESC LIMIT 1
"""
keywords_result = await conn.fetchrow(keywords_query, template['id'])
keywords = []
if keywords_result and keywords_result['keywords_json']:
keywords_data = keywords_result['keywords_json']
# Parse JSON if it's a string
if isinstance(keywords_data, str):
try:
import json
keywords = json.loads(keywords_data)
except:
keywords = []
elif isinstance(keywords_data, list):
keywords = keywords_data
print(f" 🔑 Found {len(keywords)} keywords")
# Create template node in Neo4j
template_data = dict(template)
template_data['id'] = str(template_data['id'])
await neo4j_client.create_template_node(template_data)
# Create tech stack node
tech_stack_data = {
"name": rec['stack_name'],
"category": "tech_stack",
"maturity_score": 0.9,
"learning_curve": "medium",
"performance_rating": float(rec['recommendation_score']) / 100.0
}
await neo4j_client.create_technology_node(tech_stack_data)
# Create recommendation relationship
await neo4j_client.create_recommendation_relationship(
str(template['id']),
rec['stack_name'],
"tech_stack",
float(rec['recommendation_score']) / 100.0
)
# Create individual technology nodes and relationships
tech_fields = ['frontend', 'backend', 'database', 'cloud', 'testing', 'mobile', 'devops', 'ai_ml']
for field in tech_fields:
tech_value = rec[field]
if tech_value and tech_value.strip():
# Parse JSON if it's a string
if isinstance(tech_value, str) and tech_value.startswith('{'):
try:
tech_value = json.loads(tech_value)
if isinstance(tech_value, dict):
tech_name = tech_value.get('name', str(tech_value))
else:
tech_name = str(tech_value)
except:
tech_name = str(tech_value)
else:
tech_name = str(tech_value)
# Create technology node
tech_data = {
"name": tech_name,
"category": field,
"maturity_score": 0.8,
"learning_curve": "medium",
"performance_rating": 0.8
}
await neo4j_client.create_technology_node(tech_data)
# Create relationship
await neo4j_client.create_recommendation_relationship(
str(template['id']),
tech_name,
field,
0.8
)
# Create tool node for single recommended tool
recommended_tool = rec.get('recommended_tool', '')
if recommended_tool and recommended_tool.strip():
# Create tool node
tool_data = {
"name": recommended_tool,
"category": "business_tool",
"type": "Tool",
"maturity_score": 0.8,
"learning_curve": "easy",
"performance_rating": 0.8
}
await neo4j_client.create_technology_node(tool_data)
# Create relationship
await neo4j_client.create_recommendation_relationship(
str(template['id']),
recommended_tool,
"business_tool",
0.8
)
print(f" 🔧 Created tool: {recommended_tool}")
# Create keyword relationships
if isinstance(keywords, list):
print(f" 🔑 Processing {len(keywords)} keywords: {keywords[:3]}...")
for keyword in keywords:
if keyword and keyword.strip():
await neo4j_client.create_keyword_relationship(str(template['id']), keyword)
else:
print(f" ⚠️ Keywords not in expected list format: {type(keywords)}")
# Create TemplateRecommendation node with rich data
recommendation_data = {
'stack_name': rec['stack_name'],
'description': template.get('description', ''),
'project_scale': 'medium',
'team_size': 3,
'experience_level': 'intermediate',
'confidence_score': int(rec['recommendation_score']),
'recommendation_reasons': [
f"Tech stack: {rec['stack_name']}",
f"Score: {rec['recommendation_score']}/100",
"AI-generated recommendation"
],
'key_features': [
f"Frontend: {rec.get('frontend', 'N/A')}",
f"Backend: {rec.get('backend', 'N/A')}",
f"Database: {rec.get('database', 'N/A')}",
f"Cloud: {rec.get('cloud', 'N/A')}"
],
'estimated_development_time_months': rec.get('development_time', 3),
'complexity_level': 'medium',
'budget_range_usd': f"${rec.get('monthly_cost', 0):.0f} - ${rec.get('setup_cost', 0):.0f}",
'time_to_market_weeks': rec.get('development_time', 3) * 4,
'scalability_requirements': 'moderate',
'security_requirements': 'standard',
'success_rate_percentage': rec.get('success_rate', 85),
'user_satisfaction_score': rec.get('satisfaction', 85)
}
await neo4j_client.create_template_recommendation_node(str(template['id']), recommendation_data)
print(f" 📋 Created TemplateRecommendation node")
print(f" ✅ Successfully migrated to Neo4j")
await conn.close()
await neo4j_client.close()
print("\n🎉 MIGRATION COMPLETED!")
print(f"📊 Successfully migrated: {len(templates)} templates")
print("🔗 Neo4j knowledge graph created with tech stack relationships")
except Exception as e:
print(f"❌ Migration failed: {e}")
# ============================================================================
# MAIN EXECUTION
# ============================================================================
if __name__ == "__main__":
import sys
if len(sys.argv) > 1 and sys.argv[1] == "migrate":
# Run migration
asyncio.run(migrate_to_neo4j())
elif len(sys.argv) > 2 and sys.argv[1] == "--template-id":
# Generate recommendations for specific template
template_id = sys.argv[2]
# Configure logger to output to stderr for command line usage
import logging
logging.basicConfig(level=logging.ERROR, stream=sys.stderr)
async def get_recommendation():
try:
claude_client = ClaudeClient()
result = await claude_client.get_recommendation(template_id)
# Only output JSON to stdout
print(json.dumps(result, default=str))
except Exception as e:
error_result = {
"error": str(e),
"template_id": template_id
}
print(json.dumps(error_result))
asyncio.run(get_recommendation())
else:
# Start FastAPI server
uvicorn.run(
app,
host="0.0.0.0",
port=8013,
log_level="info"
)