ML Engine Updates: - Updated BTCUSD with Polygon API data (2024-2025): 215,699 new records - Re-trained all ML models: Attention (R²: 0.223), Base, Metamodel (87.3% confidence) - Backtest results: +176.71R profit with aggressive_filter strategy Documentation Consolidation: - Created docs/99-analisis/_MAP.md index with 13 new analysis documents - Consolidated inventories: removed duplicates from orchestration/inventarios/ - Updated ML_INVENTORY.yml with BTCUSD metrics and training results - Added execution reports: FASE11-BTCUSD, correction issues, alignment validation Architecture & Integration: - Updated all module documentation with NEXUS v3.4 frontmatter - Fixed _MAP.md indexes across all folders - Updated orchestration plans and traces Files: 229 changed, 5064 insertions(+), 1872 deletions(-) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
765 lines
23 KiB
Markdown
765 lines
23 KiB
Markdown
---
|
|
id: "ET-LLM-004"
|
|
title: "Integración con Módulo Educativo"
|
|
type: "Technical Specification"
|
|
status: "Done"
|
|
priority: "Alta"
|
|
epic: "OQI-007"
|
|
project: "trading-platform"
|
|
version: "1.0.0"
|
|
created_date: "2025-12-05"
|
|
updated_date: "2026-01-04"
|
|
---
|
|
|
|
# ET-LLM-004: Integración con Módulo Educativo
|
|
|
|
**Épica:** OQI-007 - LLM Strategy Agent
|
|
**Versión:** 1.0
|
|
**Fecha:** 2025-12-05
|
|
**Estado:** Planificado
|
|
**Prioridad:** P1 - Alto
|
|
|
|
---
|
|
|
|
## Resumen
|
|
|
|
Esta especificación define cómo el agente LLM se integra con el módulo educativo (OQI-002) para proporcionar asistencia personalizada, explicaciones contextuales y recomendaciones de aprendizaje.
|
|
|
|
---
|
|
|
|
## Arquitectura de Integración
|
|
|
|
```
|
|
┌─────────────────────────────────────────────────────────────────────────┐
|
|
│ EDUCATION INTEGRATION LAYER │
|
|
├─────────────────────────────────────────────────────────────────────────┤
|
|
│ │
|
|
│ ┌────────────────────┐ ┌────────────────────┐ │
|
|
│ │ OQI-002 │ │ OQI-007 │ │
|
|
│ │ Education Module │ │ LLM Agent │ │
|
|
│ │ │ │ │ │
|
|
│ │ ┌──────────────┐ │ │ ┌──────────────┐ │ │
|
|
│ │ │ Courses │◄─┼─────┼─►│ Education │ │ │
|
|
│ │ │ Service │ │ │ │ Assistant │ │ │
|
|
│ │ └──────────────┘ │ │ │ Agent │ │ │
|
|
│ │ │ │ └──────────────┘ │ │
|
|
│ │ ┌──────────────┐ │ │ │ │ │
|
|
│ │ │ Progress │◄─┼─────┼─────────┘ │ │
|
|
│ │ │ Tracker │ │ │ │ │
|
|
│ │ └──────────────┘ │ │ ┌──────────────┐ │ │
|
|
│ │ │ │ │ Learning │ │ │
|
|
│ │ ┌──────────────┐ │ │ │ Recommender │ │ │
|
|
│ │ │ Content │◄─┼─────┼─►│ │ │ │
|
|
│ │ │ Library │ │ │ └──────────────┘ │ │
|
|
│ │ └──────────────┘ │ │ │ │
|
|
│ └────────────────────┘ └────────────────────┘ │
|
|
│ │
|
|
└─────────────────────────────────────────────────────────────────────────┘
|
|
```
|
|
|
|
---
|
|
|
|
## Componentes
|
|
|
|
### 1. Education Context Builder
|
|
|
|
```typescript
|
|
// src/modules/copilot/education/context-builder.ts
|
|
|
|
interface EducationContext {
|
|
currentCourse: CourseInfo | null;
|
|
currentLesson: LessonInfo | null;
|
|
overallProgress: ProgressSummary;
|
|
recentQuizzes: QuizResult[];
|
|
strugglingTopics: Topic[];
|
|
masteredTopics: Topic[];
|
|
suggestedNextSteps: Suggestion[];
|
|
}
|
|
|
|
interface CourseInfo {
|
|
id: string;
|
|
title: string;
|
|
level: 'beginner' | 'intermediate' | 'advanced';
|
|
progress: number;
|
|
currentModule: string;
|
|
}
|
|
|
|
interface LessonInfo {
|
|
id: string;
|
|
title: string;
|
|
type: 'video' | 'reading' | 'interactive';
|
|
concepts: string[];
|
|
duration: number;
|
|
completed: boolean;
|
|
}
|
|
|
|
@Injectable()
|
|
export class EducationContextBuilder {
|
|
constructor(
|
|
private readonly courseService: CourseService,
|
|
private readonly progressService: ProgressService,
|
|
) {}
|
|
|
|
async build(userId: string): Promise<EducationContext> {
|
|
const [
|
|
enrollments,
|
|
progress,
|
|
quizResults,
|
|
analytics,
|
|
] = await Promise.all([
|
|
this.courseService.getUserEnrollments(userId),
|
|
this.progressService.getUserProgress(userId),
|
|
this.courseService.getRecentQuizzes(userId, 10),
|
|
this.progressService.getLearningAnalytics(userId),
|
|
]);
|
|
|
|
// Find current course (most recently accessed)
|
|
const currentEnrollment = enrollments
|
|
.filter(e => e.status === 'in_progress')
|
|
.sort((a, b) => b.lastAccessedAt.getTime() - a.lastAccessedAt.getTime())[0];
|
|
|
|
let currentCourse: CourseInfo | null = null;
|
|
let currentLesson: LessonInfo | null = null;
|
|
|
|
if (currentEnrollment) {
|
|
const course = await this.courseService.getCourse(currentEnrollment.courseId);
|
|
const lesson = await this.courseService.getCurrentLesson(
|
|
userId,
|
|
currentEnrollment.courseId,
|
|
);
|
|
|
|
currentCourse = {
|
|
id: course.id,
|
|
title: course.title,
|
|
level: course.level,
|
|
progress: currentEnrollment.progress,
|
|
currentModule: lesson?.moduleName || 'N/A',
|
|
};
|
|
|
|
if (lesson) {
|
|
currentLesson = {
|
|
id: lesson.id,
|
|
title: lesson.title,
|
|
type: lesson.type,
|
|
concepts: lesson.concepts,
|
|
duration: lesson.duration,
|
|
completed: lesson.completed,
|
|
};
|
|
}
|
|
}
|
|
|
|
// Analyze struggling topics
|
|
const strugglingTopics = this.identifyStrugglingTopics(quizResults, analytics);
|
|
const masteredTopics = this.identifyMasteredTopics(quizResults, analytics);
|
|
|
|
// Generate suggestions
|
|
const suggestedNextSteps = await this.generateSuggestions(
|
|
userId,
|
|
strugglingTopics,
|
|
progress,
|
|
);
|
|
|
|
return {
|
|
currentCourse,
|
|
currentLesson,
|
|
overallProgress: progress.summary,
|
|
recentQuizzes: quizResults.slice(0, 5),
|
|
strugglingTopics,
|
|
masteredTopics,
|
|
suggestedNextSteps,
|
|
};
|
|
}
|
|
|
|
private identifyStrugglingTopics(
|
|
quizResults: QuizResult[],
|
|
analytics: LearningAnalytics,
|
|
): Topic[] {
|
|
const topicScores = new Map<string, number[]>();
|
|
|
|
// Aggregate quiz scores by topic
|
|
for (const result of quizResults) {
|
|
for (const topic of result.topics) {
|
|
const scores = topicScores.get(topic) || [];
|
|
scores.push(result.topicScores[topic] || result.score);
|
|
topicScores.set(topic, scores);
|
|
}
|
|
}
|
|
|
|
// Identify topics with consistently low scores
|
|
const struggling: Topic[] = [];
|
|
for (const [topic, scores] of topicScores) {
|
|
const avgScore = scores.reduce((a, b) => a + b, 0) / scores.length;
|
|
if (avgScore < 70 && scores.length >= 2) {
|
|
struggling.push({
|
|
name: topic,
|
|
averageScore: avgScore,
|
|
attempts: scores.length,
|
|
lastAttempt: this.findLastAttemptDate(quizResults, topic),
|
|
});
|
|
}
|
|
}
|
|
|
|
return struggling.sort((a, b) => a.averageScore - b.averageScore);
|
|
}
|
|
|
|
private async generateSuggestions(
|
|
userId: string,
|
|
strugglingTopics: Topic[],
|
|
progress: UserProgress,
|
|
): Promise<Suggestion[]> {
|
|
const suggestions: Suggestion[] = [];
|
|
|
|
// Suggest review for struggling topics
|
|
for (const topic of strugglingTopics.slice(0, 3)) {
|
|
const relatedLesson = await this.courseService.findLessonByTopic(topic.name);
|
|
if (relatedLesson) {
|
|
suggestions.push({
|
|
type: 'review',
|
|
priority: 'high',
|
|
title: `Revisar: ${topic.name}`,
|
|
description: `Tu puntuación promedio es ${topic.averageScore}%. Te recomiendo repasar este tema.`,
|
|
action: {
|
|
type: 'goto_lesson',
|
|
lessonId: relatedLesson.id,
|
|
lessonTitle: relatedLesson.title,
|
|
},
|
|
});
|
|
}
|
|
}
|
|
|
|
// Suggest next lesson if current course in progress
|
|
const nextLesson = await this.progressService.getNextLesson(userId);
|
|
if (nextLesson) {
|
|
suggestions.push({
|
|
type: 'continue',
|
|
priority: 'medium',
|
|
title: `Continuar: ${nextLesson.title}`,
|
|
description: `Estás en el ${progress.summary.currentCourseProgress}% del curso.`,
|
|
action: {
|
|
type: 'goto_lesson',
|
|
lessonId: nextLesson.id,
|
|
lessonTitle: nextLesson.title,
|
|
},
|
|
});
|
|
}
|
|
|
|
// Suggest practice if no recent activity
|
|
if (progress.summary.daysSinceLastActivity > 3) {
|
|
suggestions.push({
|
|
type: 'practice',
|
|
priority: 'medium',
|
|
title: 'Práctica recomendada',
|
|
description: 'Han pasado algunos días. ¿Qué tal un poco de práctica?',
|
|
action: {
|
|
type: 'start_practice',
|
|
topics: strugglingTopics.map(t => t.name),
|
|
},
|
|
});
|
|
}
|
|
|
|
return suggestions;
|
|
}
|
|
}
|
|
```
|
|
|
|
### 2. Education Assistant Agent
|
|
|
|
```typescript
|
|
// src/modules/copilot/education/assistant-agent.ts
|
|
|
|
const EDUCATION_SYSTEM_PROMPT = `
|
|
# Education Assistant - Trading Platform Academy
|
|
|
|
## Tu Rol
|
|
Eres un tutor personalizado de trading, especializado en ayudar a los usuarios
|
|
a aprender conceptos de trading e inversión. Tu objetivo es hacer que el aprendizaje
|
|
sea accesible, interesante y aplicable al mundo real.
|
|
|
|
## Contexto del Usuario
|
|
{user_education_context}
|
|
|
|
## Principios de Enseñanza
|
|
|
|
1. **Adapta al nivel:** Ajusta la complejidad de tus explicaciones al nivel del usuario
|
|
2. **Usa ejemplos reales:** Conecta conceptos con situaciones de mercado actuales
|
|
3. **Fomenta la práctica:** Sugiere ejercicios en paper trading
|
|
4. **Sé paciente:** Si el usuario no entiende, intenta explicar de forma diferente
|
|
5. **Celebra el progreso:** Reconoce los logros del usuario
|
|
|
|
## Reglas Importantes
|
|
|
|
1. NUNCA des respuestas directas a quizzes o evaluaciones
|
|
2. Si detectas confusión persistente, sugiere revisar lecciones específicas
|
|
3. Siempre relaciona la teoría con la práctica
|
|
4. No hagas promesas de rentabilidad
|
|
5. Usa analogías simples para conceptos complejos
|
|
6. Si no conoces un tema específico del curso, admítelo
|
|
|
|
## Herramientas Disponibles
|
|
|
|
- get_lesson_content(lessonId): Obtener contenido de una lección
|
|
- get_user_progress(): Ver progreso del usuario
|
|
- find_related_lessons(topic): Buscar lecciones relacionadas a un tema
|
|
- get_practice_questions(topic): Obtener preguntas de práctica
|
|
- get_market_example(concept): Buscar ejemplo real en mercado actual
|
|
|
|
## Formato de Respuesta para Explicaciones
|
|
|
|
### Explicación de Concepto
|
|
1. Definición simple (1-2 oraciones)
|
|
2. Analogía o metáfora
|
|
3. Ejemplo con mercado real
|
|
4. Cómo aplicarlo en trading
|
|
5. Recurso adicional (lección relacionada)
|
|
|
|
### Asistencia en Quiz (sin dar respuestas)
|
|
1. Clarificar qué se pregunta
|
|
2. Recordar conceptos relevantes
|
|
3. Dar pista indirecta
|
|
4. Sugerir revisar sección específica
|
|
`;
|
|
|
|
@Injectable()
|
|
export class EducationAssistantAgent {
|
|
constructor(
|
|
private readonly llmProvider: LLMProviderService,
|
|
private readonly contextBuilder: EducationContextBuilder,
|
|
private readonly lessonService: LessonService,
|
|
private readonly marketDataService: MarketDataService,
|
|
) {}
|
|
|
|
async processEducationQuery(
|
|
userId: string,
|
|
query: string,
|
|
conversationHistory: Message[],
|
|
): Promise<AsyncGenerator<StreamChunk>> {
|
|
// Build education context
|
|
const educationContext = await this.contextBuilder.build(userId);
|
|
|
|
// Format context for system prompt
|
|
const formattedContext = this.formatEducationContext(educationContext);
|
|
|
|
// Detect query intent
|
|
const intent = await this.detectIntent(query);
|
|
|
|
// Build tools based on intent
|
|
const tools = this.getToolsForIntent(intent);
|
|
|
|
// Create completion
|
|
const systemPrompt = EDUCATION_SYSTEM_PROMPT.replace(
|
|
'{user_education_context}',
|
|
formattedContext,
|
|
);
|
|
|
|
return this.llmProvider.createChatCompletion({
|
|
model: 'gpt-4o',
|
|
messages: [
|
|
{ role: 'system', content: systemPrompt },
|
|
...conversationHistory,
|
|
{ role: 'user', content: query },
|
|
],
|
|
tools,
|
|
stream: true,
|
|
});
|
|
}
|
|
|
|
private formatEducationContext(context: EducationContext): string {
|
|
let formatted = '';
|
|
|
|
if (context.currentCourse) {
|
|
formatted += `## Curso Actual\n`;
|
|
formatted += `- **Curso:** ${context.currentCourse.title}\n`;
|
|
formatted += `- **Nivel:** ${context.currentCourse.level}\n`;
|
|
formatted += `- **Progreso:** ${context.currentCourse.progress}%\n`;
|
|
formatted += `- **Módulo actual:** ${context.currentCourse.currentModule}\n\n`;
|
|
}
|
|
|
|
if (context.currentLesson) {
|
|
formatted += `## Lección Actual\n`;
|
|
formatted += `- **Lección:** ${context.currentLesson.title}\n`;
|
|
formatted += `- **Conceptos:** ${context.currentLesson.concepts.join(', ')}\n`;
|
|
formatted += `- **Estado:** ${context.currentLesson.completed ? 'Completada' : 'En progreso'}\n\n`;
|
|
}
|
|
|
|
if (context.strugglingTopics.length > 0) {
|
|
formatted += `## Temas con Dificultad\n`;
|
|
for (const topic of context.strugglingTopics) {
|
|
formatted += `- ${topic.name} (promedio: ${topic.averageScore}%)\n`;
|
|
}
|
|
formatted += '\n';
|
|
}
|
|
|
|
if (context.masteredTopics.length > 0) {
|
|
formatted += `## Temas Dominados\n`;
|
|
formatted += context.masteredTopics.map(t => t.name).join(', ') + '\n\n';
|
|
}
|
|
|
|
if (context.suggestedNextSteps.length > 0) {
|
|
formatted += `## Sugerencias de Siguiente Paso\n`;
|
|
for (const suggestion of context.suggestedNextSteps) {
|
|
formatted += `- [${suggestion.priority}] ${suggestion.title}\n`;
|
|
}
|
|
}
|
|
|
|
return formatted;
|
|
}
|
|
|
|
private async detectIntent(query: string): Promise<EducationIntent> {
|
|
// Simple keyword-based intent detection
|
|
const lowerQuery = query.toLowerCase();
|
|
|
|
if (lowerQuery.includes('quiz') || lowerQuery.includes('examen') ||
|
|
lowerQuery.includes('respuesta')) {
|
|
return 'quiz_help';
|
|
}
|
|
|
|
if (lowerQuery.includes('qué es') || lowerQuery.includes('explica') ||
|
|
lowerQuery.includes('cómo funciona')) {
|
|
return 'concept_explanation';
|
|
}
|
|
|
|
if (lowerQuery.includes('ejemplo') || lowerQuery.includes('práctica')) {
|
|
return 'practical_example';
|
|
}
|
|
|
|
if (lowerQuery.includes('siguiente') || lowerQuery.includes('qué debo')) {
|
|
return 'learning_path';
|
|
}
|
|
|
|
return 'general_help';
|
|
}
|
|
|
|
private getToolsForIntent(intent: EducationIntent): Tool[] {
|
|
const baseTools = [
|
|
{
|
|
type: 'function',
|
|
function: {
|
|
name: 'get_lesson_content',
|
|
description: 'Obtiene el contenido de una lección específica',
|
|
parameters: {
|
|
type: 'object',
|
|
properties: {
|
|
lessonId: { type: 'string' },
|
|
},
|
|
required: ['lessonId'],
|
|
},
|
|
},
|
|
},
|
|
{
|
|
type: 'function',
|
|
function: {
|
|
name: 'find_related_lessons',
|
|
description: 'Busca lecciones relacionadas a un tema',
|
|
parameters: {
|
|
type: 'object',
|
|
properties: {
|
|
topic: { type: 'string' },
|
|
},
|
|
required: ['topic'],
|
|
},
|
|
},
|
|
},
|
|
];
|
|
|
|
if (intent === 'practical_example') {
|
|
baseTools.push({
|
|
type: 'function',
|
|
function: {
|
|
name: 'get_market_example',
|
|
description: 'Busca un ejemplo real en el mercado actual',
|
|
parameters: {
|
|
type: 'object',
|
|
properties: {
|
|
concept: { type: 'string' },
|
|
},
|
|
required: ['concept'],
|
|
},
|
|
},
|
|
});
|
|
}
|
|
|
|
if (intent === 'quiz_help') {
|
|
baseTools.push({
|
|
type: 'function',
|
|
function: {
|
|
name: 'get_practice_questions',
|
|
description: 'Obtiene preguntas de práctica (no del quiz oficial)',
|
|
parameters: {
|
|
type: 'object',
|
|
properties: {
|
|
topic: { type: 'string' },
|
|
difficulty: { type: 'string', enum: ['easy', 'medium', 'hard'] },
|
|
},
|
|
required: ['topic'],
|
|
},
|
|
},
|
|
});
|
|
}
|
|
|
|
return baseTools;
|
|
}
|
|
}
|
|
```
|
|
|
|
### 3. Learning Recommender
|
|
|
|
```typescript
|
|
// src/modules/copilot/education/learning-recommender.ts
|
|
|
|
interface LearningRecommendation {
|
|
type: 'course' | 'lesson' | 'practice' | 'review';
|
|
item: CourseItem | LessonItem | PracticeItem;
|
|
reason: string;
|
|
priority: number;
|
|
estimatedTime: number; // minutes
|
|
}
|
|
|
|
@Injectable()
|
|
export class LearningRecommender {
|
|
constructor(
|
|
private readonly courseService: CourseService,
|
|
private readonly progressService: ProgressService,
|
|
private readonly analyticsService: LearningAnalyticsService,
|
|
) {}
|
|
|
|
async getRecommendations(
|
|
userId: string,
|
|
limit: number = 5,
|
|
): Promise<LearningRecommendation[]> {
|
|
const [
|
|
progress,
|
|
analytics,
|
|
availableCourses,
|
|
completedCourses,
|
|
] = await Promise.all([
|
|
this.progressService.getUserProgress(userId),
|
|
this.analyticsService.getUserAnalytics(userId),
|
|
this.courseService.getAvailableCourses(userId),
|
|
this.courseService.getCompletedCourses(userId),
|
|
]);
|
|
|
|
const recommendations: LearningRecommendation[] = [];
|
|
|
|
// 1. Continue current course
|
|
if (progress.currentCourse) {
|
|
const nextLesson = await this.progressService.getNextLesson(userId);
|
|
if (nextLesson) {
|
|
recommendations.push({
|
|
type: 'lesson',
|
|
item: nextLesson,
|
|
reason: 'Continúa donde lo dejaste',
|
|
priority: 100,
|
|
estimatedTime: nextLesson.duration,
|
|
});
|
|
}
|
|
}
|
|
|
|
// 2. Review struggling topics
|
|
const strugglingTopics = analytics.strugglingTopics || [];
|
|
for (const topic of strugglingTopics.slice(0, 2)) {
|
|
const reviewLesson = await this.courseService.findReviewMaterial(topic.name);
|
|
if (reviewLesson) {
|
|
recommendations.push({
|
|
type: 'review',
|
|
item: reviewLesson,
|
|
reason: `Refuerza tu comprensión de ${topic.name}`,
|
|
priority: 90 - topic.averageScore,
|
|
estimatedTime: 15,
|
|
});
|
|
}
|
|
}
|
|
|
|
// 3. Practice recommendations
|
|
if (analytics.daysSinceLastPractice > 2) {
|
|
const practiceTopics = this.selectPracticeTopics(analytics);
|
|
recommendations.push({
|
|
type: 'practice',
|
|
item: {
|
|
topics: practiceTopics,
|
|
questionCount: 10,
|
|
},
|
|
reason: 'La práctica hace al maestro',
|
|
priority: 70,
|
|
estimatedTime: 20,
|
|
});
|
|
}
|
|
|
|
// 4. Next course recommendation
|
|
if (progress.currentCourse?.progress > 80) {
|
|
const nextCourse = this.selectNextCourse(
|
|
completedCourses,
|
|
availableCourses,
|
|
analytics,
|
|
);
|
|
if (nextCourse) {
|
|
recommendations.push({
|
|
type: 'course',
|
|
item: nextCourse,
|
|
reason: 'Siguiente paso en tu aprendizaje',
|
|
priority: 50,
|
|
estimatedTime: nextCourse.totalDuration,
|
|
});
|
|
}
|
|
}
|
|
|
|
// Sort by priority and limit
|
|
return recommendations
|
|
.sort((a, b) => b.priority - a.priority)
|
|
.slice(0, limit);
|
|
}
|
|
|
|
private selectPracticeTopics(analytics: LearningAnalytics): string[] {
|
|
const topics: string[] = [];
|
|
|
|
// Mix of struggling and mastered topics (70/30 ratio)
|
|
const struggling = (analytics.strugglingTopics || []).slice(0, 3);
|
|
const mastered = (analytics.masteredTopics || []).slice(0, 1);
|
|
|
|
topics.push(...struggling.map(t => t.name));
|
|
topics.push(...mastered.map(t => t.name));
|
|
|
|
return topics;
|
|
}
|
|
|
|
private selectNextCourse(
|
|
completed: Course[],
|
|
available: Course[],
|
|
analytics: LearningAnalytics,
|
|
): Course | null {
|
|
// Filter out completed courses
|
|
const completedIds = new Set(completed.map(c => c.id));
|
|
const candidates = available.filter(c => !completedIds.has(c.id));
|
|
|
|
if (candidates.length === 0) return null;
|
|
|
|
// Score candidates based on prerequisites and user level
|
|
const scored = candidates.map(course => {
|
|
let score = 0;
|
|
|
|
// Check prerequisites
|
|
const prereqsMet = course.prerequisites.every(p => completedIds.has(p));
|
|
if (!prereqsMet) return { course, score: -1 };
|
|
|
|
// Score based on level progression
|
|
const levelOrder = ['beginner', 'intermediate', 'advanced'];
|
|
const userLevel = analytics.estimatedLevel || 'beginner';
|
|
const courseLevel = course.level;
|
|
|
|
const userLevelIndex = levelOrder.indexOf(userLevel);
|
|
const courseLevelIndex = levelOrder.indexOf(courseLevel);
|
|
|
|
if (courseLevelIndex === userLevelIndex) score += 50;
|
|
if (courseLevelIndex === userLevelIndex + 1) score += 30;
|
|
|
|
// Score based on topic interest
|
|
const userInterests = analytics.topicInterests || [];
|
|
const matchingTopics = course.topics.filter(t =>
|
|
userInterests.includes(t)
|
|
).length;
|
|
score += matchingTopics * 10;
|
|
|
|
return { course, score };
|
|
});
|
|
|
|
const best = scored
|
|
.filter(s => s.score >= 0)
|
|
.sort((a, b) => b.score - a.score)[0];
|
|
|
|
return best?.course || null;
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Tools para el Agente
|
|
|
|
### get_lesson_content
|
|
|
|
```typescript
|
|
async function getLessonContent(lessonId: string): Promise<LessonContent> {
|
|
const lesson = await lessonService.getLesson(lessonId);
|
|
|
|
return {
|
|
id: lesson.id,
|
|
title: lesson.title,
|
|
summary: lesson.summary,
|
|
keyPoints: lesson.keyPoints,
|
|
concepts: lesson.concepts,
|
|
examples: lesson.examples,
|
|
relatedLessons: lesson.relatedLessons.map(l => ({
|
|
id: l.id,
|
|
title: l.title,
|
|
})),
|
|
};
|
|
}
|
|
```
|
|
|
|
### get_market_example
|
|
|
|
```typescript
|
|
async function getMarketExample(concept: string): Promise<MarketExample> {
|
|
// Map concepts to market patterns
|
|
const conceptPatterns: Record<string, string[]> = {
|
|
'soporte': ['support_bounce', 'support_break'],
|
|
'resistencia': ['resistance_rejection', 'breakout'],
|
|
'rsi': ['oversold_bounce', 'overbought_reversal'],
|
|
'macd': ['bullish_crossover', 'bearish_crossover'],
|
|
'tendencia': ['uptrend', 'downtrend'],
|
|
};
|
|
|
|
const patterns = conceptPatterns[concept.toLowerCase()] || [];
|
|
|
|
// Find recent example in market
|
|
const example = await marketDataService.findPatternExample(patterns, {
|
|
lookbackDays: 30,
|
|
preferLiquid: true,
|
|
});
|
|
|
|
if (!example) {
|
|
return {
|
|
found: false,
|
|
message: `No encontré un ejemplo reciente de ${concept} en el mercado.`,
|
|
};
|
|
}
|
|
|
|
return {
|
|
found: true,
|
|
symbol: example.symbol,
|
|
pattern: example.pattern,
|
|
date: example.date,
|
|
description: example.description,
|
|
chartUrl: `/charts/${example.symbol}?highlight=${example.date}`,
|
|
};
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Dependencias
|
|
|
|
### Módulos Internos
|
|
- CourseService (OQI-002)
|
|
- ProgressService (OQI-002)
|
|
- LessonService (OQI-002)
|
|
- MarketDataService (OQI-003)
|
|
|
|
### Base de Datos
|
|
- courses
|
|
- lessons
|
|
- enrollments
|
|
- lesson_progress
|
|
- quiz_results
|
|
|
|
---
|
|
|
|
## Referencias
|
|
|
|
- [RF-LLM-004: Asistencia Educativa](../requerimientos/RF-LLM-004-educational-assistance.md)
|
|
- [OQI-002: Módulo Educativo](../../OQI-002-education/)
|
|
|
|
---
|
|
|
|
*Especificación técnica - Sistema NEXUS*
|
|
*Trading Platform*
|