324 lines
10 KiB
JavaScript
324 lines
10 KiB
JavaScript
const express = require('express');
|
|
const router = express.Router();
|
|
const database = require('../config/database');
|
|
const Feature = require('../models/feature');
|
|
const Template = require('../models/template');
|
|
|
|
// POST /api/learning/feature-selected - Track feature selection for learning
|
|
router.post('/feature-selected', async (req, res) => {
|
|
try {
|
|
const { template_id, feature_id, user_session, project_id } = req.body;
|
|
|
|
console.log(`🧠 Learning: Feature selected - ${feature_id} for template ${template_id}`);
|
|
|
|
// Validate required fields
|
|
if (!template_id || !feature_id) {
|
|
return res.status(400).json({
|
|
success: false,
|
|
error: 'Missing required fields',
|
|
message: 'template_id and feature_id are required'
|
|
});
|
|
}
|
|
|
|
// Record the selection
|
|
const query = `
|
|
INSERT INTO feature_usage (template_id, feature_id, user_session, project_id)
|
|
VALUES ($1, $2, $3, $4)
|
|
RETURNING *
|
|
`;
|
|
|
|
const result = await database.query(query, [template_id, feature_id, user_session, project_id]);
|
|
|
|
// Update feature usage count
|
|
const updateQuery = `
|
|
UPDATE template_features
|
|
SET usage_count = usage_count + 1
|
|
WHERE id = $1
|
|
`;
|
|
await database.query(updateQuery, [feature_id]);
|
|
|
|
res.json({
|
|
success: true,
|
|
data: result.rows[0],
|
|
message: 'Feature selection recorded for learning system'
|
|
});
|
|
} catch (error) {
|
|
console.error('❌ Error recording feature selection:', error.message);
|
|
res.status(500).json({
|
|
success: false,
|
|
error: 'Failed to record feature selection',
|
|
message: error.message
|
|
});
|
|
}
|
|
});
|
|
|
|
// GET /api/learning/recommendations/:templateId - Get AI-powered recommendations
|
|
router.get('/recommendations/:templateId', async (req, res) => {
|
|
try {
|
|
const { templateId } = req.params;
|
|
const limit = parseInt(req.query.limit) || 5;
|
|
|
|
console.log(`🤖 Generating recommendations for template: ${templateId}`);
|
|
|
|
// Get template info
|
|
const template = await Template.getByIdWithFeatures(templateId);
|
|
if (!template) {
|
|
return res.status(404).json({
|
|
success: false,
|
|
error: 'Template not found',
|
|
message: `Template with ID ${templateId} does not exist`
|
|
});
|
|
}
|
|
|
|
// Get popular features from similar templates (same category)
|
|
const similarFeaturesQuery = `
|
|
SELECT
|
|
tf.*,
|
|
t.title as template_title,
|
|
t.type as template_type
|
|
FROM template_features tf
|
|
JOIN templates t ON tf.template_id = t.id
|
|
WHERE t.category = (
|
|
SELECT category FROM templates WHERE id = $1
|
|
)
|
|
AND tf.template_id != $1
|
|
AND tf.usage_count > 0
|
|
ORDER BY tf.usage_count DESC, tf.user_rating DESC
|
|
LIMIT $2
|
|
`;
|
|
|
|
const similarFeatures = await database.query(similarFeaturesQuery, [templateId, limit]);
|
|
|
|
// Get trending features (high recent usage)
|
|
const trendingQuery = `
|
|
SELECT
|
|
tf.*,
|
|
t.title as template_title,
|
|
COUNT(fu.id) as recent_usage
|
|
FROM template_features tf
|
|
JOIN templates t ON tf.template_id = t.id
|
|
LEFT JOIN feature_usage fu ON tf.id = fu.feature_id
|
|
AND fu.selected_at > NOW() - INTERVAL '30 days'
|
|
WHERE tf.template_id != $1
|
|
GROUP BY tf.id, t.title
|
|
HAVING COUNT(fu.id) > 0
|
|
ORDER BY recent_usage DESC, tf.user_rating DESC
|
|
LIMIT $2
|
|
`;
|
|
|
|
const trendingFeatures = await database.query(trendingQuery, [templateId, limit]);
|
|
|
|
// Get complementary features (often used together)
|
|
const complementaryQuery = `
|
|
WITH template_sessions AS (
|
|
SELECT DISTINCT user_session
|
|
FROM feature_usage
|
|
WHERE template_id = $1
|
|
AND user_session IS NOT NULL
|
|
)
|
|
SELECT
|
|
tf.*,
|
|
t.title as template_title,
|
|
COUNT(DISTINCT ts.user_session) as co_occurrence
|
|
FROM template_sessions ts
|
|
JOIN feature_usage fu ON ts.user_session = fu.user_session
|
|
JOIN template_features tf ON fu.feature_id = tf.id
|
|
JOIN templates t ON tf.template_id = t.id
|
|
WHERE tf.template_id != $1
|
|
GROUP BY tf.id, t.title
|
|
ORDER BY co_occurrence DESC, tf.user_rating DESC
|
|
LIMIT $2
|
|
`;
|
|
|
|
const complementaryFeatures = await database.query(complementaryQuery, [templateId, limit]);
|
|
|
|
const recommendations = {
|
|
similar_features: similarFeatures.rows,
|
|
trending_features: trendingFeatures.rows,
|
|
complementary_features: complementaryFeatures.rows,
|
|
template_info: {
|
|
id: template.id,
|
|
title: template.title,
|
|
category: template.category,
|
|
existing_features_count: template.features ? template.features.length : 0
|
|
}
|
|
};
|
|
|
|
res.json({
|
|
success: true,
|
|
data: recommendations,
|
|
message: `Generated ${Object.keys(recommendations).length - 1} types of recommendations for ${template.title}`
|
|
});
|
|
} catch (error) {
|
|
console.error('❌ Error generating recommendations:', error.message);
|
|
res.status(500).json({
|
|
success: false,
|
|
error: 'Failed to generate recommendations',
|
|
message: error.message
|
|
});
|
|
}
|
|
});
|
|
|
|
// POST /api/learning/analyze-usage - Analyze usage patterns
|
|
router.post('/analyze-usage', async (req, res) => {
|
|
try {
|
|
const { template_id, time_period = '30 days' } = req.body;
|
|
|
|
console.log(`📊 Analyzing usage patterns for template: ${template_id || 'all'}`);
|
|
|
|
let templateFilter = '';
|
|
const params = [time_period];
|
|
|
|
if (template_id) {
|
|
templateFilter = 'AND fu.template_id = $2';
|
|
params.push(template_id);
|
|
}
|
|
|
|
// Usage trends over time
|
|
const trendsQuery = `
|
|
SELECT
|
|
DATE(fu.selected_at) as date,
|
|
COUNT(*) as selections,
|
|
COUNT(DISTINCT fu.user_session) as unique_sessions
|
|
FROM feature_usage fu
|
|
WHERE fu.selected_at > NOW() - INTERVAL '${time_period}'
|
|
${templateFilter}
|
|
GROUP BY DATE(fu.selected_at)
|
|
ORDER BY date DESC
|
|
LIMIT 30
|
|
`;
|
|
|
|
// Most popular features
|
|
const popularQuery = `
|
|
SELECT
|
|
tf.name,
|
|
tf.feature_type,
|
|
tf.complexity,
|
|
COUNT(fu.id) as usage_count,
|
|
COUNT(DISTINCT fu.user_session) as unique_users,
|
|
t.title as template_title
|
|
FROM feature_usage fu
|
|
JOIN template_features tf ON fu.feature_id = tf.id
|
|
JOIN templates t ON fu.template_id = t.id
|
|
WHERE fu.selected_at > NOW() - INTERVAL '${time_period}'
|
|
${templateFilter}
|
|
GROUP BY tf.id, tf.name, tf.feature_type, tf.complexity, t.title
|
|
ORDER BY usage_count DESC
|
|
LIMIT 20
|
|
`;
|
|
|
|
// Feature type distribution
|
|
const distributionQuery = `
|
|
SELECT
|
|
tf.feature_type,
|
|
tf.complexity,
|
|
COUNT(fu.id) as selections,
|
|
COUNT(DISTINCT fu.user_session) as unique_users
|
|
FROM feature_usage fu
|
|
JOIN template_features tf ON fu.feature_id = tf.id
|
|
WHERE fu.selected_at > NOW() - INTERVAL '${time_period}'
|
|
${templateFilter}
|
|
GROUP BY tf.feature_type, tf.complexity
|
|
ORDER BY selections DESC
|
|
`;
|
|
|
|
const [trendsResult, popularResult, distributionResult] = await Promise.all([
|
|
database.query(trendsQuery, params),
|
|
database.query(popularQuery, params),
|
|
database.query(distributionQuery, params)
|
|
]);
|
|
|
|
const analysis = {
|
|
time_period,
|
|
template_id: template_id || 'all',
|
|
usage_trends: trendsResult.rows,
|
|
popular_features: popularResult.rows,
|
|
feature_distribution: distributionResult.rows,
|
|
summary: {
|
|
total_trends: trendsResult.rows.length,
|
|
top_features: popularResult.rows.length,
|
|
distribution_segments: distributionResult.rows.length
|
|
}
|
|
};
|
|
|
|
res.json({
|
|
success: true,
|
|
data: analysis,
|
|
message: `Usage analysis completed for ${time_period} period`
|
|
});
|
|
} catch (error) {
|
|
console.error('❌ Error analyzing usage patterns:', error.message);
|
|
res.status(500).json({
|
|
success: false,
|
|
error: 'Failed to analyze usage patterns',
|
|
message: error.message
|
|
});
|
|
}
|
|
});
|
|
|
|
// GET /api/learning/insights - Get learning system insights
|
|
router.get('/insights', async (req, res) => {
|
|
try {
|
|
console.log('🔍 Gathering learning system insights...');
|
|
|
|
// Overall statistics
|
|
const statsQuery = `
|
|
SELECT
|
|
COUNT(DISTINCT fu.template_id) as active_templates,
|
|
COUNT(DISTINCT fu.feature_id) as features_used,
|
|
COUNT(DISTINCT fu.user_session) as unique_sessions,
|
|
COUNT(*) as total_selections
|
|
FROM feature_usage fu
|
|
WHERE fu.selected_at > NOW() - INTERVAL '30 days'
|
|
`;
|
|
|
|
// Growth metrics
|
|
const growthQuery = `
|
|
SELECT
|
|
DATE_TRUNC('week', fu.selected_at) as week,
|
|
COUNT(*) as selections,
|
|
COUNT(DISTINCT fu.user_session) as users
|
|
FROM feature_usage fu
|
|
WHERE fu.selected_at > NOW() - INTERVAL '12 weeks'
|
|
GROUP BY DATE_TRUNC('week', fu.selected_at)
|
|
ORDER BY week DESC
|
|
`;
|
|
|
|
// Learning effectiveness
|
|
const effectivenessQuery = `
|
|
SELECT
|
|
AVG(tf.user_rating) as avg_rating,
|
|
SUM(CASE WHEN tf.created_by_user = true THEN 1 ELSE 0 END) as user_contributed_features,
|
|
SUM(CASE WHEN tf.is_default = true THEN 1 ELSE 0 END) as default_features
|
|
FROM template_features tf
|
|
`;
|
|
|
|
const [statsResult, growthResult, effectivenessResult] = await Promise.all([
|
|
database.query(statsQuery),
|
|
database.query(growthQuery),
|
|
database.query(effectivenessQuery)
|
|
]);
|
|
|
|
const insights = {
|
|
overview: statsResult.rows[0],
|
|
growth_trends: growthResult.rows,
|
|
learning_effectiveness: effectivenessResult.rows[0],
|
|
generated_at: new Date().toISOString()
|
|
};
|
|
|
|
res.json({
|
|
success: true,
|
|
data: insights,
|
|
message: 'Learning system insights generated successfully'
|
|
});
|
|
} catch (error) {
|
|
console.error('❌ Error gathering insights:', error.message);
|
|
res.status(500).json({
|
|
success: false,
|
|
error: 'Failed to gather learning insights',
|
|
message: error.message
|
|
});
|
|
}
|
|
});
|
|
|
|
module.exports = router; |